Поиск и модификация текстового содержимого, с применением регулярных выражений

Дата создания: 23.10.2010
Дата изменения: 22.10.2012 (внесены изменения в код, существенно ускоряющие процесс итерации по объектам)
Состояние: завершена

    На форуме dwg.ru тема мусолилась, однако я так и не увидел грамотных решений, касающихся поиска и модификации текстовой информации, сохранённой в однострочном, многострочном текстах, а так же в атрибутах вхождений блоков и в мультивыносках. 

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

    Издавна подобные задачи решались с помощью регулярных выражений. Этот механизм сейчас реализован во многих языках программирования, но изначально, наиболее мощная и удобная его реализация была в языке Perl. Сейчас она есть и в др. языках. В .Net реализация механизма работы с регулярными выражениями не уступает Perl по силе. Именно регулярными выражениями следует пользоваться в подобных задачах, касающихся обработки текста.

Примечание

    
Небольшое отступление от общей темы (благодаря которому я, скорее всего, вызову волну возмущения у ряда "автолисперов", но это отступление некоторым образом поясняет, почему на dwg.ru нет нормального решения по данной теме (имхо)):

    Основание выше указанной "проблемы" в том, что многие "акадовские" программисты пытаются дать решение на Auto/Visual Lisp, в котором работа с регулярными выражениями мягко говоря не развита, зачастую мотивируя свои упорные попытки дать решение именно на AutoLisp тем, что "Lisp, мол один из самых лучших языков", а следовательно на нём проблему решить можно. Однако в этой фразе замаскирована ложь (софизм), ибо язык Lisp существует во множестве диалектов как самостоятельный, полноценный язык программирования и действительно является одним из самых мощных языков, однако никак нельзя ставить знак равенства между тем же Common Lisp и реализацией "лиспа", предоставленной компанией Autodesk - Auto/Visual Lisp, функционал которого оставляет желать лучшего.

    Подход "всё_на_свете_делать_с_помощью_AutoLisp" (весьма популярный у "автолисперов") - является по меньшей мере нецелесообразным. Можно конечно и зубы через одно место выдёргивать, но, имхо, через рот это делать всё же удобнее...

    Для решения той или иной задачи нужно выбирать инструменты, наиболее пригодные для её решения. Указанная мною задача демонстрирует один из таких примеров - решать её на Auto/Visual Lisp нецелесообразно в виду отсутствия необходимого набора библиотек, заточенных под подобные задачи (имхо). Знать один прикладной язык программирования - неплохо. Но знание других языков расширяет "инструментальный набор" программиста.

    Решение задачи, рассматриваемой на этой странице сайта, можно было бы дать на языке Perl, но поскольку данный сайт посвящён .Net - решение будет дано на C#.
 
    Я предполагаю, что в Common Lisp наверняка имеется механизм работы с регулярными выражениями, поскольку это полноценный (не прикладной) язык программирования. Однако, к сожалению, я не знаю данного замечательного во всех отношениях (говорю на полном серьёзе) языка, как не знают его (опять же - к сожалению) и 99,9% "автолисперов", хотя кому-кому, а им бы это знание ой как развязало руки во многих вопросах, позволив выйти за рамки "прикладной песочницы" очерченной компанией Autodesk (тем более, что синтаксис языка им уже знаком). 

    Время идёт, Autodesk каждый год штампует очередную версию AutoCAD, ObjectARX потихоньку меняется, а у Auto/Visual Lisp - песочница всё та же, границы её не расширяются. Похоже на то, что Autodesk негласно "забил болт" на Auto/Visual Lisp, сделав ставку на ObjectARX (в первую очередь), ну и конечно же на .Net, библиотеки которой всё более и более "причёсываются" Autodesk'ом в порядок от версии к версии AutoCAD. Почему Autodesk не развивает Auto/Visual Lisp - это вопрос не ко мне...

Задача

    Итак, нужна команда, которая запрашивала бы у пользователя правило фильтрации и правило модификации выбранного результата. Поиск должен происходить не только в пространстве Модели, но и по всем листам. Обрабатываться должны однострочный и многострочный тексты, атрибуты вхождений блоков и мультивыноски.

Решение

    Ниже - озвученное видео, демонстрирующее работу некой команды ReplaceByRegex
    Ещё ниже - полный исходный код программы и откомпилированная версия библиотеки.

Видео YouTube


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

Для своих пользователей я прикручу графический интерфейс, в котором можно будет видеть предварительные результаты работы указанных фильтров, с возможностью указания того, где именно следует искать объекты (в модели/на листах/на конкретном листе/др. комбинация), с возможностью указания анализируемых примитивов с помощью пользовательского выбора, а так же с возможностью указания конкретного типа объектов, которые должны анализироваться (однострочный текст/многострочный текст/атрибуты/мультивыноски). 

Для общей демонстрации применения "регексов" (собственно для этого здесь и выложил) - хватит и данного решения.

Код C#

  1:  //************************************************************************************************
   2:  //Разработчик: Андрей Бушман
   3:  //Страна: Россия
   4:  //Город: Санкт-Петербург
   5:  //E-mail: Console.WriteLine("{0}.{1}@{2}.{3}", "bushman", "andrey", "gmail", "com");
   6:  //************************************************************************************************
   7:  //Microsoft
   8:  using System;
   9:  using System.Collections.Generic;
  10:  using System.Linq;
  11:  using System.Xml.Linq;
  12:  using System.Text;
  13:  using System.IO;
  14:  using System.Reflection;
  15:  using System.Text.RegularExpressions;
  16:  //Autodesk
  17:  using acad = Autodesk.AutoCAD.ApplicationServices.Application;
  18:  using Autodesk.AutoCAD.ApplicationServices;
  19:  using Autodesk.AutoCAD.DatabaseServices;
  20:  using Autodesk.AutoCAD.EditorInput;
  21:  using Autodesk.AutoCAD.Runtime;
  22:  //Bushman
  23:   
  24:  namespace Bushman.AutoCAD.Common {
  25:      public class Class1 {
  26:          DocumentCollection dm = acad.DocumentManager;
  27:          Document dwg;
  28:          Database db;
  29:          Editor ed;
  30:          const string ns = "mycad";//Пространство команд
  31:   
  32:          /// <summary>
  33:          /// Конструктор по умолчанию
  34:          /// </summary>
  35:          public Class1() {
  36:              Drawing = acad.DocumentManager.MdiActiveDocument;
  37:              curAssembly = this.GetType().Assembly;
  38:              curDir = new FileInfo(CurrentAssembly.Location).Directory;
  39:          }
  40:   
  41:          DirectoryInfo curDir;
  42:          /// <summary>
  43:          /// Каталог, в котором находится сборка, содержащая данный класс
  44:          /// </summary>
  45:          public DirectoryInfo CurrentDirectory {
  46:              get {
  47:                  return curDir;
  48:              }
  49:          }
  50:   
  51:          Assembly curAssembly;
  52:          /// <summary>
  53:          /// Сборка, в состав которой входит данный класс
  54:          /// </summary>
  55:          public Assembly CurrentAssembly {
  56:              get {
  57:                  return curAssembly;
  58:              }
  59:          }
  60:   
  61:          /// <summary>
  62:          /// Чертёж
  63:          /// </summary>
  64:          Document Drawing {
  65:              get {
  66:                  return dwg;
  67:              }
  68:              set {
  69:                  dwg = value;
  70:                  if (dwg != null) {
  71:                      db = dwg.Database;
  72:                      ed = dwg.Editor;
  73:                  }
  74:                  else {
  75:                      db = null;
  76:                      ed = null;
  77:                  }
  78:              }
  79:          }
  80:   
  81:          /// <summary>
  82:          /// Поиск с заменой, используя регулярные выражения
  83:          /// </summary>
  84:          [CommandMethod(ns, "ReplaceByRegex", CommandFlags.Modal)]
  85:          public void ReplaceByRegex() {
  86:              PromptStringOptions pso = new PromptStringOptions("\nУкажите фильтр (регулярное выражение), согласно которому следует выполнить выборку");
  87:              pso.AllowSpaces = true;
  88:              PromptResult pr = ed.GetString(pso);
  89:              string filter = string.Empty;
  90:              string replaceRule = string.Empty;
  91:              if (pr.Status == PromptStatus.OK) {
  92:                  filter = pr.StringResult;
  93:              }
  94:              else {
  95:                  return;
  96:              }
  97:              pso = new PromptStringOptions("\nУкажите правило (регулярное выражение), согласно которому следует модифицировать выбранные значения");
  98:              pso.AllowSpaces = true;
  99:              pr = ed.GetString(pso);
 100:              if (pr.Status == PromptStatus.OK) {
 101:                  replaceRule = pr.StringResult;
 102:              }
 103:              else {
 104:                  return;
 105:              }
 106:              //Изменяю текстовое содержимое в однострочных, многострочных текстах, а так же в атрибутах вхождений блоков
 107:              ObjectId[] primitives = ModifyText(filter, replaceRule);
 108:          }
 109:   
 110:          /// <summary>
 111:          /// Получить идентификаторы тех примитивов, которые содержат текст, удовлетворяющий условию, переданному в качестве параметра
 112:          /// </summary>
 113:          /// <param name="filter">Регулярное выражение, на основании которого производится фильтрация нужного контента</param>
 114:          /// <param name="replaceRule">Правило, согласно которому следует модифицировать содержимое текста</param>
 115:          /// <returns>Возвращается коллекция идентификаторов объектов, соответствующих указанному условию</returns>
 116:          public ObjectId[] ModifyText(string filter, string replaceRule) {
 117:              List<Type> types = new List<Type>() { typeof(MText), typeof(DBText), typeof(AttributeReference), typeof(MLeader) };
 118:              List<ObjectId> primitives = new List<ObjectId>();
 119:   
 120:              using (Transaction t = dwg.TransactionManager.StartTransaction()) {
 121:                  for (long i = db.BlockTableId.Handle.Value; i < db.Handseed.Value; i++) {
 122:                      ObjectId id = ObjectId.Null;
 123:   
 124:                      if (!db.TryGetObjectId(new Handle(i), out id))
 125:                          continue;
 126:   
 127:                      if (!id.IsErased && types.Contains(t.GetObject(id, OpenMode.ForRead).GetType())) {
 128:                          DBObject x = t.GetObject(id, OpenMode.ForWrite);
 129:                          Regex regex = new Regex(filter);
 130:                          if (x is MText) {
 131:                              MText mtext = (MText)x;
 132:                              bool result = Regex.IsMatch(mtext.Contents, filter, RegexOptions.IgnoreCase);
 133:                              if (result) {
 134:                                  mtext.Contents = regex.Replace(mtext.Contents, replaceRule);
 135:                                  primitives.Add(id);
 136:                              }
 137:                          }
 138:                          else if (x is DBText) {
 139:                              DBText text = (DBText)x;
 140:                              bool result = Regex.IsMatch(text.TextString, filter, RegexOptions.IgnoreCase);
 141:                              if (result) {
 142:                                  text.TextString = regex.Replace(text.TextString, replaceRule);
 143:                                  primitives.Add(id);
 144:                              }
 145:                          }
 146:                          else if (x is MLeader) {
 147:                              MLeader ml = (MLeader)x;
 148:                              bool result = Regex.IsMatch(ml.MText.Contents, filter, RegexOptions.IgnoreCase);
 149:                              if (result) {
 150:                                  string value = regex.Replace(ml.MText.Contents, replaceRule);
 151:                                  MText mtxt = new MText();
 152:                                  mtxt.SetDatabaseDefaults();
 153:                                  mtxt.Contents = value;
 154:                                  ml.MText = mtxt;
 155:                                  BlockTableRecord btr = (BlockTableRecord)t.GetObject(ml.OwnerId, OpenMode.ForWrite);
 156:                                  btr.AppendEntity(mtxt);
 157:                                  t.AddNewlyCreatedDBObject(mtxt, true);
 158:                                  primitives.Add(id);
 159:                              }
 160:                          }
 161:                      }
 162:                  }
 163:                  t.Commit();
 164:              }
 165:              return primitives.ToArray();
 166:          }
 167:      }
 168:  }

    Для работы плагина, на компьютере должен быть установлен AutoCAD 2009 x86 SP3, а так же бесплатная библиотека .Net Framework 3.5 SP1

    Исходный код здесь (проект MS VS 2010).
    Откомпилированная для AutoCAD 2009 библиотека здесь (загружается в AutoCAD командой NETLOAD из командной строки AutoCAD). 

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

Внимание!

    Если вы не имеете ни малейшего представления о регулярных выражениях, то прежде всего прочтите хотя бы пару глав этой книги (на сие действо у вас уйдёт два вечера, не более). 

Внимание 2!

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

Comments