Шаблоны документов Excel

5 2

Удалось обойти диалог создания версий документа из шаблона и полностью его сохранить, так что это действительно дополнение Шаблонов Excel без потери стандартного функционал.

  1.  Пример использования.
  2.  Предназначение.
  3.  Для этой реализации.
  4.  Тонкости.
  5.  Реализация: (п.1, п.2, п.3, п.4, п.5, п.6, п.7, п.8, п.9)

Реализация автоматического заполнения шаблонов документов Excel будет показана на примере библиотеки Aspose.Cells. Думаю реализовать можно на любой другой похожей библиотеке. Получить библиотеку можно на официальном сайте Aspose. Но для самостоятельной разработки новой функциональности потребуется купить лицензию на Aspose.

Пример использования

К примеру у вас есть шаблон документа Excel со своими макросами/базами/функциями и т.д.... С помощью данной реализации вы сможете заполнить шаблон легко и просто (насколько это возможно). 

Пример одного из возможных шаблонов:

 


Предназначение

  • Расширение стандартного функционала "Шаблоны документов" до возможности работать с документами Excel

 

Для этой реализации

  • Потребуется установка библиотеки Aspose.Cells
  • Перекрыть один из основных документов к примеру InternalDocumentBase или можно раньше или вообще любой другой
  • Создать тип документа "DocumentTemplateExcel" или перекрыть существующий "DocumentTemplate"

 

Тонкости

  • Если создали всё же наследника шаблонов, следите что бы не было задвоений в двух типах шаблонах, а то пользователю покажет при выборе 2 из разных шаблонов документов и может не отработать логика т.к. будет выбран не тот шаблон (шаблон у которого нет параметров)
  • После создания документа из шаблона по новому методу: создаётся PublicBody у документа с исправленными ключами и создаётся просто Body где хранится сам шаблон.
  • После загрузки версии в шаблоны документов, нужно перезайти в карточку или воспользоваться действиями предоставленными ниже.
  • Принимает только маски вида  "[*]" "<*>" "#*#"  где * любое количество символов(чувствителен к регистру) (Дорабатывается в изолированной области)
  • Но-код -Для самостоятельного дополнения: нужно править клиентскую функцию "GetValuesExpressionNoCodeParametersTemplate"

 

Реализация

  1. Добавьте библиотеку Aspose.Cells (или любую другую) в модуля где будут храниться функции изолированной области 
  2. Добавьте новую изолированную область AsposeFunctions (или отредактируйте свою)  
  3. В новой области AsposeFunctions добавьте:
    Функции:

        /// <summary>
        /// Добавить значения в макет документа.
        /// </summary>
        /// <param name="document">Поток документа</param>
        /// <param name="extension">Расширение документа</param>
        /// <param name="key">Ключ. Пример: "[подразделение]". key может быть любой строкой</param>
        /// <param name="val">Значение. Пример: "Имя подразделения"</param>
        /// <remarks>Ищит по документу схожий ключ и заменяет при удачном поиске.</remarks>
        /// <remarks>Для сохранения\показа\открытия используйте перекрытие функции AddValuesToDocumentLayout</remarks>
        /// <remarks>Значения заменяются по одному, а иначе изалированная область не принимает коллекцию</remarks>
        /// <returns>Поток изменённого документа</returns>
        [Public]
        public virtual System.IO.Stream AddValuesToLayout(System.IO.Stream document, string extension, string key, string val)
        {
          var asposeFunctions = GetAsposeFunctions();
          return asposeFunctions.AddValuesToLayout(document, extension, key, val);
        }
        
    
        
        
        [Public]
        public virtual List<string> SearchKeysTemplate(System.IO.Stream document, string extension)
        {
          var asposeFunctions = GetAsposeFunctions();
          return asposeFunctions.SearchKeysTemplate(document, extension);
        }
        
        
        
        /// <summary>
        /// Создать средство работы с функциями кода обласи.
        /// </summary>
        /// <returns>.</returns>
        public virtual Basic.Isolated.AsposeFunctions.Functions GetAsposeFunctions()
        {
          return new AsposeFunctions.Functions();
        }

    Код области:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;
    using Sungero.Core;
    using dkcao.Basic.Structures.Module;
    using Aspose.BarCode;
    using Aspose.BarCode.Generation;
    using Aspose.Words;
    using Aspose.Cells;
    using SkiaSharp;
    using System.IO;
    
    namespace dkcao.Basic.Isolated.AsposeFunctions
    {
      public class Functions
      {
        /// <summary>
        /// Добавить значения в макет документа.
        /// </summary>
        /// <param name="document">Поток документа</param>
        /// <param name="extension">Расширение документа</param>
        /// <param name="key">Ключ. Пример: "[подразделение]". key может быть любой строкой</param>
        /// <param name="val">Значение. Пример: "Имя подразделения"</param>
        /// <remarks>Ищит по документу схожий ключ и заменяет при удачном поиске.</remarks>
        /// <remarks>Для сохранения\показа\открытия используйте перекрытие функции AddValuesToDocumentLayout</remarks>
        /// <remarks>Значения заменяются по одному, а иначе изалированная область не принимает коллекцию</remarks>
        /// <returns>Поток изменённого документа</returns>
        public virtual System.IO.MemoryStream AddValuesToLayout(System.IO.Stream document, string extension, string key, string val)
        {
          bool isWord = (extension == "doc" || extension == "docx");
          bool isExcel = (extension == "xls" || extension == "xlsx");
          
          
          
          
          if (isWord)
          {
            Aspose.Words.Document doc = new Aspose.Words.Document(document);
            // Опции для поиска
            var findReplaceOptions = new Aspose.Words.Replacing.FindReplaceOptions();
            // Чувствительность к регистру
            findReplaceOptions.MatchCase = true;
            // Замена найденного текста, если он не является частью другого слова.
            findReplaceOptions.FindWholeWordsOnly = true;
            // Если нашёл, то вернёт количество замен.
            int countReplace = doc.Range.Replace(key, val, findReplaceOptions);
            
            System.IO.MemoryStream newDocStream = new System.IO.MemoryStream();
            // Сохраните документ Word
            doc.Save(newDocStream, Aspose.Words.SaveFormat.Docx);
            return newDocStream;
          }
          
          if (isExcel)
          {
            Aspose.Cells.Workbook wb = new Aspose.Cells.Workbook(document);
            // Доступ к листу по его индексу
            Aspose.Cells.Worksheet sheet = wb.Worksheets[0];
            //Создаем экземпляр объекта FindOptions
            Aspose.Cells.FindOptions findOptions = new Aspose.Cells.FindOptions();
            findOptions.LookInType = Aspose.Cells.LookInType.Values;
            // Чувствительность к регистру
            findOptions.CaseSensitive = true;
            // Поищим клетку
            Cell cell = sheet.Cells.Find(key, null, findOptions);
            // Если клетка найдена заменим значение
            if (cell != null)
              cell.Value = val;
            
            // Заменим все одинаковые ключи
            while (cell != null)
            {
              var prevCell = cell;
              // Поищим клетку
              cell = sheet.Cells.Find(key, prevCell, findOptions);
              // Если клетка найдена запишем значение
              if (cell != null)
                cell.Value = val;
            }
            
            
            System.IO.MemoryStream newDocStream = new System.IO.MemoryStream();
            // Сохраните документ Excel
            wb.Save(newDocStream, Aspose.Cells.SaveFormat.Xlsx);
            return newDocStream;
          }
          
          return new System.IO.MemoryStream();
        }
        
        public virtual System.IO.Stream AddValuesToLayout(System.IO.Stream document, string extension, IList<string> l)
        {
          return new System.IO.MemoryStream();
        }
        
        
        public virtual List<string> SearchKeysTemplate(System.IO.Stream document, string extension)
        {
          bool isWord = (extension == "doc" || extension == "docx");
          bool isExcel = (extension == "xls" || extension == "xlsx");
          
          // Возможные ключи
          string[] whats = new string[] { "<*>", "[*]", "#*#"};
          
          var list = new List<string>();
          
          if (isExcel)
          {
            Workbook wb = new Workbook(document);
            // Доступ к листу по его индексу
            Worksheet sheet = wb.Worksheets[0];
            //Создаем экземпляр объекта FindOptions
            FindOptions findOptions = new FindOptions
            {
              LookInType = LookInType.Values,
              // LookAtType = LookAtType.Contains
              // Чувствительность к регистру
              // CaseSensitive = true;
            };
            
            // Пройдёмся по всем ключам поиска
            foreach (var what in whats)
            {
              // Поищим клетку
              Cell cell = sheet.Cells.Find(what, null, findOptions);
              // Если клетка найдена запишем значение
              if (cell != null && !string.IsNullOrEmpty(cell.StringValue))
                list.Add(cell.StringValue);
              
              while (cell != null)
              {
                var prevCell = cell;
                // Поищим клетку
                cell = sheet.Cells.Find(what, prevCell, findOptions);
                // Если клетка найдена запишем значение
                if (cell != null && !string.IsNullOrEmpty(cell.StringValue))
                  list.Add(cell.StringValue);
              }
            }
    
            return list;
          }
          
          return null;
        }
        
      }
    }



  4. Добавьте клиентские функции в модуль или любое другое место:

     #region Aspose    
        
        /// <summary>
        /// Добавить значения в макет документа
        /// </summary>
        /// <param name="document">Документ</param>
        /// <param name="values">Значения. Пример: TKey = "[подразделение]" TValue = "Имя подразделения"</param>
        /// <param name="newVersion">Добавление в новую версию документа</param>
        /// <param name="needSave">Сохранение после добавлений изменений в версию</param>
        /// <param name="needOpen">Открыть измененный документ на просмотр</param>
        /// <remarks>Ищет по документу схожие значения ключей и заменяет при удачном поиске.</remarks>
        /// <remarks>Если newVersion = false, то обязательно должно быть needSave = true, а то создатся новая версия</remarks>
        /// <remarks>TKey может быть любой строкой</remarks>
        /// <returns>Если всё удачно то null, а иначе ошибка</returns>
        [Public]
        public string AddValuesToDocumentLayout(IOfficialDocument document, System.Collections.Generic.IDictionary<string, string> values, bool newVersion, bool needSave, bool needOpen)
        {
          if (document == null)
            return "Не заполнен документ.";
          if (!document.HasVersions)
            return "Нет версии у документа";
    
          var lastVers = document.LastVersion;
          string extension = lastVers.AssociatedApplication.Extension;
          
          // Поток для сохранения значений из изолированной области
          var mainStream = new System.IO.MemoryStream();
          
          try
          {
    
            using (var streamBody = lastVers.Body.Read())
            {
              streamBody.CopyTo(mainStream);
            }
            
            // Переберём весь лист ключей и значений
            foreach (var keyAndValue in values)
            {
              var key = keyAndValue.Key;
              var val = keyAndValue.Value;
              using (var stream = Basic.Functions.Module.AddValuesToDocumentLayout(mainStream, extension, key, val))
              {
                // Очистим поток и перезапишем
                mainStream.Position = 0;
                mainStream.SetLength(0);
                stream.CopyTo(mainStream);
              }
            }
            
            // Если нужно сохранить в новой версии
            if (newVersion)
            {
              document.CreateVersionFrom(mainStream, extension);
            }
            else if (needSave)
            {
              // Должно быть обязательно сохранено, так что только если сохраняется запись после добавления значений
              lastVers.Body.Write(mainStream);
              document.Save();
            }
            // Иначе в любом случае новая версия. А то закроется поток и версия не сохранится
            else
              document.CreateVersionFrom(mainStream, extension);
            
            if (document.State.IsChanged && needSave)
              document.Save();
            
            // Если нужно открыть на просмотр
            if (needOpen)
            {
              using (var openStream = new System.IO.MemoryStream())
              {
                mainStream.CopyTo(openStream);
                // откроем документ на просмотр не сохраняя
                openStream.ToArray().Open($"{document.Name}." + extension);
              }
            }
            
    
            mainStream.Dispose();
          }
          catch (Exception ex)
          {
            mainStream.Dispose();
            return ex.Message;
          }
          
          return null;
        }
        
        
        /// <summary>
        /// Добавить значения в версию документа
        /// </summary>
        /// <param name="stream">Поток версии</param>
        /// <param name="extension">Расширение версии</param>
        /// <param name="values">Значения. Пример: TKey = "[подразделение]" TValue = "Имя подразделения"</param>
        /// <param name="needOpen">Открыть измененный документ на просмотр</param>
        /// <remarks>Ищет по документу схожие значения ключей и заменяет при удачном поиске.</remarks>
        /// <remarks>TKey может быть любой строкой</remarks>
        /// <returns>Если всё удачно то изменённый поток, а иначе null</returns>
        [Public]
        public System.IO.Stream AddValuesToDocumentLayout(System.IO.Stream stream, string extension, System.Collections.Generic.IDictionary<string, string> values, bool needOpen)
        {
          
          // Поток для сохранения значений из изолированной области
          var mainStream = new System.IO.MemoryStream();
          
          try
          {
            stream.Position = 0;
            stream.CopyTo(mainStream);
            
            // Переберём весь лист ключей и значений
            foreach (var keyAndValue in values)
            {
              var key = keyAndValue.Key;
              var val = keyAndValue.Value;
              //          mainStream.Position = 0;
              using (var newStream = Basic.Functions.Module.AddValuesToDocumentLayout(mainStream, extension, key, val))
              {
                // Очистим поток и перезапишем
                mainStream.Position = 0;
                mainStream.SetLength(0);
                newStream.CopyTo(mainStream);
                
                
              }
            }
            
            // Если нужно открыть на просмотр
            if (needOpen)
            {
              using (var openStream = new System.IO.MemoryStream())
              {
                mainStream.Position = 0;
                mainStream.CopyTo(openStream);
                // откроем документ на просмотр не сохраняя
                openStream.ToArray().Open($"Документ: " + extension);
              }
            }
            
            //        mainStream.Position = 0;
            return mainStream;
          }
          catch (Exception ex)
          {
            mainStream.Dispose();
            return null;
          }
          
        }
        
        #endregion
    
    
     #region Разбор выражений NoCode
        
        /// <summary>
        /// Получить значения выражений но-кода параметров шаблона
        /// </summary>
        /// <param name="MainObj">Основная сущность</param>
        /// <param name="parameters">Параметры шаблона документа</param>
        /// <returns>Коллекция параметров и их значения</returns>
        /// <remarks>TKey - Имя параметра
        /// TValue - Определённое значение выражения но-код</remarks>
        [Public]
        public static System.Collections.Generic.IDictionary<string, string> GetValuesExpressionNoCodeParametersTemplate(Sungero.Domain.Shared.IEntity MainObj, Sungero.Domain.Shared.IChildEntityCollection<Sungero.Content.IElectronicDocumentParameters> parameters)
        {
          return GetValuesExpressionNoCodeParametersTemplate(MainObj, parameters, false, null);
        }
        
        /// <summary>
        /// Получить значения выражений но-кода параметров шаблона
        /// </summary>
        /// <param name="MainObj">Основная сущность</param>
        /// <param name="parameters">Параметры шаблона документа</param>
        /// <param name="returnNullVal">Вернуть нулевые значения</param>
        /// <param name="defaultNullVal">Значение по умолчание если нет значения</param>
        /// <returns>Коллекция: параметров и их значения</returns>
        /// <remarks>TKey - Имя параметра
        /// TValue - Определённое значение выражения но-код</remarks>
        [Public]
        public static System.Collections.Generic.IDictionary<string, string> GetValuesExpressionNoCodeParametersTemplate(Sungero.Domain.Shared.IEntity MainObj, Sungero.Domain.Shared.IChildEntityCollection<Sungero.Content.IElectronicDocumentParameters> parameters, bool returnNullVal, string defaultNullVal)
        {
          
          IDictionary<string, string> dictiParams = new Dictionary<string, string>();
          IDictionary<string, string> values = new Dictionary<string, string>();
          
          // Если параметров нет, то завершаем
          if (!parameters.Any())
            return values;
    
          dictiParams = parameters.ToDictionary(p => p.Name, p => p.DataSource);
          
          foreach (var param in dictiParams)
          {
            // Заменим выражение значением
            if (string.IsNullOrEmpty(param.Value))
            {
              if (returnNullVal)
                values.Add(param.Key, string.IsNullOrEmpty(defaultNullVal) ? string.Empty : defaultNullVal);
            }
            else
            {
              var expression = param.Value;
              var expressions = expression.Split(new string[] { "->" }, StringSplitOptions.None);
    
              IEntity entity = null;
              string val = string.Empty;
              string previousVal = string.Empty;
              bool first = true;
              int count = expressions.Count();
              Sungero.Metadata.PropertyType propType = Sungero.Metadata.PropertyType.String;
              
              
              foreach (var expres in expressions)
              {
                
                // Если круг не первый и нет entity, то завершаем (где-то null)
                if (!first && entity == null && string.IsNullOrEmpty(val))
                {
                  if (returnNullVal)
                    values.Add(param.Key, string.IsNullOrEmpty(defaultNullVal) ? string.Empty : defaultNullVal);
    
                  break;
                }
    
                // Данные типа "<Guid типа сущности>:<Guid свойства>
                if (expres.StartsWith("{") && expres.Contains(":"))
                {
                  var dataSource = expres.Replace("{", "").Replace("}", "");
                  var guids = dataSource.Split(':');
                  var typeGuid = Guid.Parse(guids[0]); // Guid типа сущности
                  var propertyGuid = Guid.Parse(guids[1]); // Guid свойства
                  var type = TypeExtension.GetTypeByGuid(typeGuid);
                  var typeMetadata = type.GetEntityMetadata();
                  var property = typeMetadata.GetProperty(propertyGuid);
                  propType = property.PropertyType;
                  
                  // Если сущность, идем дальше
                  if (propType == Sungero.Metadata.PropertyType.Navigation)
                  {
                    if (entity == null)
                    {
                      entity = property.GetValue<IEntity>(MainObj);
                    }
                    else
                    {
                      entity = property.GetValue<IEntity>(entity);
                    }
                    
                    first = false;
                    continue;
                  }
                  
                  
                  
                  // Если не сущность, то берём значение
                  if (first)
                    val = property.GetValue(MainObj)?.ToString();
                  else if (entity != null)
                    val = property.GetValue(entity)?.ToString();
                  
                  
                  
                  values.Add(param.Key, val);
                  first = false;
                  continue;
                }
                
                string newVal = string.Empty;
                
                // Если не сущность, то берём значение
                if (first)
                {
                  newVal = GetValueExpressionByNoCodeKeys(MainObj, expres, val, propType);
                  if (!string.IsNullOrEmpty(newVal))
                    val = newVal;
                  
                }
                else if (entity != null || !string.IsNullOrEmpty(val))
                {
                  newVal = GetValueExpressionByNoCodeKeys(entity, expres, val, propType);
                  if (!string.IsNullOrEmpty(newVal))
                    val = newVal;
                }
                
                if (values.ContainsKey(param.Key))
                  values[param.Key] = val;
                else
                  values.Add(param.Key, val);
                
              }
              
            }
          }
          
          return values;
        }
        
        public static string GetValueExpressionByNoCodeKeys(Sungero.Domain.Shared.IEntity obj, string keyNoCode, string val, Sungero.Metadata.PropertyType propType)
        {
          string result = string.Empty;
          CommonLibrary.PersonFullName personalData;
          
          // В верхнем регистре
          if (keyNoCode == "Uppercase")
            if (!string.IsNullOrEmpty(val))
              result = val.ToUpper();
          
          // В нижнем регистре
          if (keyNoCode == "Lowercase")
            if (!string.IsNullOrEmpty(val))
              result = val.ToLower();
          
          // С большой буквы
          if (keyNoCode == "Capitalize")
            if (!string.IsNullOrEmpty(val))
              result = val[0].ToString().ToUpper() + val.Substring(1);
          
          // Взять первую букву
          if (keyNoCode == "First")
            if (!string.IsNullOrEmpty(val))
              result = val[0].ToString();
          
          // Число прописью
          if (keyNoCode == "NumberToWords" && !string.IsNullOrEmpty(val))
          {
            long integerPart = long.MinValue;
            if (propType == Sungero.Metadata.PropertyType.Double)
              integerPart = (long)Math.Truncate(double.Parse(val));
            if (propType == Sungero.Metadata.PropertyType.LongInteger)
              integerPart = long.Parse(val);
            
            if (integerPart != long.MinValue)
              result = CommonLibrary.StringUtils.NumberToWords(integerPart);
            
          }
          
          
          
          // Фамилия Имя Отчество
          if (keyNoCode == "FullName")
          {
            if (string.IsNullOrEmpty(val))
              val = obj.DisplayValue;
            if (CommonLibrary.PersonFullName.TryParse(val, out personalData) && !string.IsNullOrEmpty(personalData.MiddleName))
            {
              personalData.DisplayFormat = CommonLibrary.PersonFullNameDisplayFormat.Full;
              result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.NotDefined);
            }
          }
    
          // Фамилия И.О.
          if (keyNoCode == "LastNameAndInitials")
          {
            if (string.IsNullOrEmpty(val))
              val = obj.DisplayValue;
            if (CommonLibrary.PersonFullName.TryParse(val, out personalData) && !string.IsNullOrEmpty(personalData.MiddleName))
            {
              personalData.DisplayFormat = CommonLibrary.PersonFullNameDisplayFormat.LastNameAndInitials;
              result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.NotDefined);
            }
          }
          
          // И.О. Фамилия
          if (keyNoCode == "InitialsAndLastName")
          {
            if (string.IsNullOrEmpty(val))
              val = obj.DisplayValue;
            if (CommonLibrary.PersonFullName.TryParse(val, out personalData) && !string.IsNullOrEmpty(personalData.MiddleName))
            {
              personalData.DisplayFormat = CommonLibrary.PersonFullNameDisplayFormat.InitialsAndLastName;
              result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.NotDefined);
            }
          }
          
          #region Склонение
          
          #region В винительном падеже (кого? что?)
          
          if (keyNoCode == "Accusative")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertCurrencyNameToTargetDeclension(val, CommonLibrary.DeclensionCase.Accusative);
          
          if (keyNoCode == "AccusativeJobTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertJobTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Accusative);
          
          if (keyNoCode == "AccusativeDepartmentTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertDepartmentTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Accusative);
          
          if (keyNoCode == "AccusativePersonalFullName")
            if (!string.IsNullOrEmpty(val))
              if (CommonLibrary.PersonFullName.TryParse(val, out personalData)) //  && !string.IsNullOrEmpty(personalData.MiddleName)
                result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.Accusative);
    
          
          
          
          
          #endregion
          
          #region В предложном падеже (о ком? о чем?)
          
          if (keyNoCode == "Prepositional")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertCurrencyNameToTargetDeclension(val, CommonLibrary.DeclensionCase.Prepositional);
          
          if (keyNoCode == "PrepositionalJobTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertJobTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Prepositional);
          
          if (keyNoCode == "PrepositionalDepartmentTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertDepartmentTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Prepositional);
          
          if (keyNoCode == "PrepositionalPersonalFullName")
            if (!string.IsNullOrEmpty(val))
              if (CommonLibrary.PersonFullName.TryParse(val, out personalData))
                result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.Prepositional);
          
          #endregion
          
          #region В дательном падеже (кому? чему?)
          
          if (keyNoCode == "Dative")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertCurrencyNameToTargetDeclension(val, CommonLibrary.DeclensionCase.Dative);
          
          if (keyNoCode == "DativeJobTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertJobTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Dative);
          
          if (keyNoCode == "DativeDepartmentTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertDepartmentTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Dative);
          
          if (keyNoCode == "DativePersonalFullName")
            if (!string.IsNullOrEmpty(val))
              if (CommonLibrary.PersonFullName.TryParse(val, out personalData))
                result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.Dative);
          
          #endregion
          
          #region В родительном падеже (кого? чего?)
          
          if (keyNoCode == "Genitive")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertCurrencyNameToTargetDeclension(val, CommonLibrary.DeclensionCase.Genitive);
          
          if (keyNoCode == "GenitiveJobTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertJobTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Genitive);
          
          if (keyNoCode == "GenitiveDepartmentTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertDepartmentTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Genitive);
          
          if (keyNoCode == "GenitivePersonalFullName")
            if (!string.IsNullOrEmpty(val))
              if (CommonLibrary.PersonFullName.TryParse(val, out personalData))
                result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.Genitive);
          
          #endregion
          
          #region В творительном падеже (кем? чем?)
    
          if (keyNoCode == "Ablative")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertCurrencyNameToTargetDeclension(val, CommonLibrary.DeclensionCase.Ablative);
          
          if (keyNoCode == "AblativeJobTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertJobTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Ablative);
          
          if (keyNoCode == "AblativeDepartmentTitle")
            if (!string.IsNullOrEmpty(val))
              result = CommonLibrary.Padeg.ConvertDepartmentTitleToTargetDeclension(val, CommonLibrary.DeclensionCase.Ablative);
          
          if (keyNoCode == "AblativePersonalFullName")
            if (!string.IsNullOrEmpty(val))
              if (CommonLibrary.PersonFullName.TryParse(val, out personalData))
                result = CaseConverter.ConvertPersonFullNameToTargetDeclension(personalData, Sungero.Core.DeclensionCase.Ablative);
          
          #endregion
          
          #endregion
          
    
          return result;
        }
        
    
        #endregion
    

     

  5. Добавьте разделяемую функцию в модуль или любое другое место:
    (не забывайте переписывать на свой модуль)

        /// <summary>
        /// Добавить значения в макет документа.
        /// </summary>
        /// <param name="document">Поток документа</param>
        /// <param name="extension">Расширение документа</param>
        /// <param name="key">Ключ. Пример: "[подразделение]". key может быть любой строкой</param>
        /// <param name="val">Значение. Пример: "Имя подразделения"</param>
        /// <remarks>Ищит по документу схожий ключ и заменяет при удачном поиске.</remarks>
        /// <remarks>Для сохранения\показа\открытия используйте перекрытие функции AddValuesToDocumentLayout</remarks>
        /// <remarks>Значения заменяются по одному, а иначе изалированная область не принимает коллекцию</remarks>
        /// <returns>Поток изменённого документа</returns>
        [Public]
        public System.IO.Stream AddValuesToDocumentLayout(System.IO.Stream document, string extension, string key, string val)
        {
          return Basic.IsolatedFunctions.AsposeFunctions.AddValuesToLayout(document, extension, key, val);
        }

     

  6. Перекройте карточку документа "DocumentTemplate" или создайте новый тип документа к примеру "DocumentTemplateExcel" (наследуясь от "Sungero.Content.ElectronicDocumentTemplate")

  7. Пройдите в свойство Body коллекции Versions и укажите событие Изменение значения свойства:

        public override void VersionsBodyChanged(Sungero.Domain.Shared.BinaryDataPropertyChangedEventArgs e)
        {
          var paramss = e.Params;
          
          var newValue = e.NewValue;
          // Получим поток, закрывать его нельзя
          var newStream = newValue.Read();
          
          // Если нет потока, то и обработка не должна быть
          if (newStream == Stream.Null)
            return;
          
          var obj = DocumentTemplateExcels.As(_obj.ElectronicDocument);
                
          var parameters = obj.Parameters;
    
          var oldParameters = new Dictionary<string, string>();
          
          oldParameters = parameters.ToDictionary(p => p.Name, p => p.DataSource);
          // Очистим, что бы пересобрать
          parameters.Clear();
          
          // Если есть версия, то ищем параметры
          var list = IsolatedFunctions.AsposeFunctions.SearchKeysTemplate(newStream, _obj.AssociatedApplication.Extension);
    
          foreach (var parameter in list)
          {
            if (parameters.Any(ps => ps.Name.Trim() == parameter.Trim()))
              continue;
            var newLine = parameters.AddNew();
            newLine.Name = parameter.Trim();
            newLine.DataSource = oldParameters.Any(p => p.Key.Trim() == parameter.Trim()) ? oldParameters.First(p => p.Key.Trim() == parameter.Trim()).Value : string.Empty;
          }
          
          
        }

     

  8. Создайте 2 действия в "DocumentTemplate..."   и добавьте их на ленту:

    RefreshParameters (Обновление параметров, а иначе нужно обновлять страницу браузера или перезаходить в карточку) и CopyValueParameter (Копирует одно значение в другое значение по имени параметра):

        public virtual void CopyValueParameter(Sungero.Domain.Client.ExecuteActionArgs e)
        {
          var dialog = Dialogs.CreateInputDialog("Перенос значения параметра из -> в (Имя параметров)");
          var iz = dialog.AddString("Из", true);
          var v = dialog.AddString("В", true);
    
          var result = dialog.Show();
          
          if (result == DialogButtons.Cancel)
            return;
          
          var izParam = _obj.Parameters.Where(p => p.Name.Trim() == iz.Value.Trim());
          var vParam = _obj.Parameters.Where(p => p.Name.Trim() == v.Value.Trim());
          
          if (izParam.Any() && vParam.Any())
            vParam.First().DataSource = izParam.First().DataSource;
        }
    
        public virtual bool CanCopyValueParameter(Sungero.Domain.Client.CanExecuteActionArgs e)
        {
          return true;
        }
    
        public virtual void RefreshParameters(Sungero.Domain.Client.ExecuteActionArgs e)
        {
          // Обновим параметры
          Locks.TryLock(_obj);
          e.CloseFormAfterAction = true;
          Locks.Unlock(_obj);
          _obj.Show();
          _obj.State.Pages.Parameters.Activate();
        }
    
        public virtual bool CanRefreshParameters(Sungero.Domain.Client.CanExecuteActionArgs e)
        {
          return !_obj.State.IsChanged;
        }




    Всё готово к работе, осталось только подвязать к какому ни будь документу.

  9. Перекройте к примеру документ InternalDocumentBase. В документе нужно прописать создание версии из шаблона в новом цвете. Для этого пройдите в свойство Body коллекции Versions и укажите событие Изменение значения свойства:

        public override void VersionsBodyChanged(Sungero.Domain.Shared.BinaryDataPropertyChangedEventArgs e)
        {
          var paramss = e.Params;
          bool isFromTemplate = false;
          long fromTemplateId = 0;      
          
          base.VersionsBodyChanged(e);
          
          // Признак того, что версия создается из шаблона.
          if (paramss.Contains(Sungero.Docflow.Constants.Module.CreateFromTemplate))
            paramss.TryGetValue(Sungero.Docflow.Constants.Module.CreateFromTemplate, out isFromTemplate);      
          
          // Если не из шаблона, то и нет обработки далее
          if (!isFromTemplate)
            return;
          
          // Получим ид шаблона
          if (paramss.Contains(Sungero.Content.Shared.ElectronicDocumentUtils.FromTemplateIdKey))
            paramss.TryGetValue(Sungero.Content.Shared.ElectronicDocumentUtils.FromTemplateIdKey, out fromTemplateId);
          
          // Если нет Ид шаблона, то завершаем
          if (fromTemplateId == 0)
            return;            
          
          #region Автоматическое заполнение Шаблона Excel из параметров Шаблона Excel
          
          var newValue = e.NewValue;
          // Получим поток, закрывать его нельзя
          var newStream = newValue.Read();
         
          var obj = InternalDocumentBases.As(_obj.ElectronicDocument);
          
          // Определим из какого шаблона создан док
          // Получим шаблон excel
          var allTemplsExcel = Basic.PublicFunctions.Module.Remote.GetAllDocumentTemplateExcel().Where(dte => dte.Id == fromTemplateId);
          var templExcel = Basic.DocumentTemplateExcels.Null;
          
          // Если нет записи, то не из Шаблонов Excel
          if (!allTemplsExcel.Any())
            return;
    
          templExcel = allTemplsExcel.First();
    
          // Получим параметры шаблона
          var parameters = templExcel.Parameters;
    
          IDictionary<string, string> values = Basic.PublicFunctions.Module.GetValuesExpressionNoCodeParametersTemplate(obj, parameters);      
     
          using (var pbStream = _obj.PublicBody.Read())
          {
            // Заполняем данные шаблона только если не было публичного тела документа
            // Т.к. создается из шаблона, публичного тела у Шаблонов Excel быть не должно, а иначе не заполнит
            // Можно отключить проверку, но бывает тут 3+ раза при создании из шаблона и каждый раз заполняет шаблон и проставляет публичное тело
            if (pbStream == Stream.Null)
            {
              var reverseStream = Basic.PublicFunctions.Module.AddValuesToDocumentLayout(newStream, templExcel.AssociatedApplication.Extension, values,  false);
              _obj.PublicBody.Write(reverseStream);
              _obj.AssociatedApplication = templExcel.AssociatedApplication; //Sungero.Content.AssociatedApplications.GetByExtension("xlsx");
            }
          }
    
          #endregion      
          
        }

     

Если все было сделано по инструкции то в результате, при создании документа из шаблона: автоматически проверятся параметры шаблона и заполнятся в Excel файл. Также данная реализация подходит для не стандартных Word документов (пример есть).

Код не переписывал под общий вид, так что ошибки будут  (Нужно адаптировать немного код под вас, а то я просто копировал и вставлял). 

 

Максим Евсеев

Сегодня выложу дополнение разбора но-кода. Почти дописал.

Максим Евсеев

Добавил разбор но-кода (п.4)

Максим Евсеев: обновлено 25.08.2025 в 12:48

Авторизуйтесь, чтобы написать комментарий