Пишем на C# Lisp-функцию, работающую с Xml-файлом (пример)

Дата публикации: 21.09.2010
Состояние: завершена
Дата редактирования: 21.09.2010

    На форуме dwg.ru я вижу ситуацию, согласно которой Lisp-программистам сложно работать с xml-файлами, т.к. они не имеют для этого соответствующих "родных" функций, а сами писать их видимо не очень хотят, хотя функционал весьма нужный (например в xml-формате удобно хранить настройки программ, хотя лисописатели зачастую предпочитают ini-файлы (как вариант - тоже неплохо))... 

    На .Net весьма просто создавать Lisp-функции, а поскольку среди .Net-технологий есть и предназначенные для работы с XML - этим можно воспользоваться, добавив в Lisp нужный функционал.

Задача

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

Структура создаваемой нами LISP-функции должна быть такой:
(FunctionName "XmlFileFullName" "ElementPath" "FilterAttributeName" "FilterValue" "AttributeName")

где:
  • FunctionName - имя функции
  • XmlFileFullName - полное имя (с указанием каталога) нашего xml-файла
  • ElementPath - путь к нашему элементу. Например, если корневым элементом нашего xml-файла является элемент Settings, а искомый нами атрибут находится в элементе MyVariable, вложенном в элемент MyVariables, который является дочерним для элемента Settings, то путь к интересующему нас элементу будет таким: "MyVariables/MyVariable". Т.е. корневой элемент Settings никогда не должен указываться в пути (специально так делаем, чтобы было меньше писать), а родительские элементы отделяются от дочерних символом "/".
  • FilterAttributeName - имя атрибута, по значению которого мы отфильтруем все элементы MyVariable (имя может быть любым) и оставим только тот, который нам нужен
  • FilterValue - значение по которому будет производиться фильтрация с использованием атрибута, указанного в параметре FilterAttributeName
  • AttributeName - имя атрибута, значение которого хотим получить
    Наша функция должна возвращать строковое значение (т.к. в xml-файле обычно хранят значения различных типов) - программист сам приведёт полученный результат к нужному ему типу. В случае ошибки или отсутствия значения должен возвращаться "" (пустая строка). Итак, пишем решение...

Код C#

   1:  //************************************************************************************************
   2:  //Developer: Andrey Bushman
   3:  //Country: Russia
   4:  //City: Saint Petersburg
   5:  //E-mail: run it code: 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.Text;
  12:  using System.Xml.Linq;
  13:  //Autodesk
  14:  using acad = Autodesk.AutoCAD.ApplicationServices.Application;
  15:  using Autodesk.AutoCAD.ApplicationServices;
  16:  using Autodesk.AutoCAD.DatabaseServices;
  17:  using Autodesk.AutoCAD.EditorInput;
  18:  using Autodesk.AutoCAD.Runtime;
  19:  using System.IO;
  20:  //Bushman
  21:   
  22:  namespace AcadLispFunctions
  23:  {
  24:      public class Class1
  25:      {
  26:          DocumentCollection dm = acad.DocumentManager;
  27:          Document dwg;
  28:          Database db;
  29:          Editor ed;
  30:          const string ns = "Bushman";//namespace of commands
  31:   
  32:          /// <summary>
  33:          /// Default constructor
  34:          /// </summary>
  35:          public Class1()
  36:          {
  37:              Drawing = acad.DocumentManager.MdiActiveDocument;
  38:          }
  39:   
  40:          /// <summary>
  41:          /// Drawing
  42:          /// </summary>
  43:          Document Drawing
  44:          {
  45:              get { return dwg; }
  46:              set
  47:              {
  48:                  dwg = value;
  49:                  if (dwg != null)
  50:                  {
  51:                      db = dwg.Database; ed = dwg.Editor;
  52:                  }
  53:                  else
  54:                  {
  55:                      db = null; ed = null;
  56:                  }
  57:              }
  58:          }
  59:   
  60:          //Lisp-функция, позволяющая получить значение нужного атрибута в составе xml-элемента указанного xml-файла.
  61:          //
  62:          //1. Первым аргументом указываем полное имя xml-файла (с каталогом), из которого нужно получить нужные данные
  63:          //2. Вторым аргументом указываем полный путь к нужному элементу. Если элемент вложен в другие элементы, 
  64:          //т.е. является дочерним, то путь к нужному элементу нужно указывать в таком формате: "Abc/Def/MyElement"
  65:          //3. третьим параметром является имя атрибута, на основании которого нужно выполнить фильтрацию содержимого
  66:          //4. четвёртый параметр - значение атрибута, указанного в п.3
  67:          //5. пятый параметр - имя атрибута, значение которого нужно получить
  68:          [LispFunction("hwd-XmlGetAttribValue")]
  69:          public TypedValue Cmd(ResultBuffer args)
  70:          {
  71:              TypedValue value = new TypedValue((short)LispDataType.Text, null);
  72:              List<TypedValue> xargs = args.Cast<TypedValue>().ToList();
  73:              int count = 5;
  74:   
  75:              string xmlFileName = string.Empty;
  76:              string[] elements;
  77:              string attribFilterName = string.Empty;
  78:              string filterValue = string.Empty;
  79:              string attribName = string.Empty;
  80:   
  81:              //Проверяю, что количество параметров равно 5
  82:              if (xargs.Count() == count && xargs.Where(n => n.TypeCode == (short)LispDataType.Text).Count() == count)
  83:              {
  84:                  xmlFileName = xargs[0].Value.ToString();                
  85:                  elements = xargs[1].Value.ToString().Split('/');
  86:                  attribFilterName = xargs[2].Value.ToString();
  87:                  filterValue = xargs[3].Value.ToString();
  88:                  attribName = xargs[4].Value.ToString();
  89:                  if (!File.Exists(xmlFileName)) return value;
  90:                  XElement xml = XElement.Load(xmlFileName);
  91:                  XElement xel = GetElement(xml, elements);
  92:                  if (xel == null || xel.Attributes().Where(n => n.Name == attribFilterName).Count() == 0) return value;
  93:                  xel = xel.Parent.Elements(xel.Name).Where(n => n.Attribute(attribFilterName).Value == filterValue).DefaultIfEmpty().First();
  94:                  if (xel == null || xel.Attributes().Where(n => n.Name == attribName).Count() == 0) return value;
  95:                  string xxx = xel.Attribute(attribName).Value;
  96:                  if (xxx == string.Empty) return value;
  97:                  value = new TypedValue((short)LispDataType.Text, xxx);                
  98:              }           
  99:              return value; 
 100:          }
 101:   
 102:          private XElement GetElement(XElement parent, string[] elements)
 103:          {
 104:              XElement result = parent;
 105:              foreach (string item in elements)
 106:              {
 107:                  result = result.Element(item);
 108:                  if (result == null) break;
 109:              }
 110:              return result;
 111:          }
 112:      }
 113:  }

    Тестируем написанный код:
Предположим, что у нас в каталоге C:\000 находится некий файл Settings.xml (можно заменить любым др. xml-файлом):

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <Settings>
   3:    <Variables>
   4:      <MyVariable Name="var1" Value="123"/>
   5:      <MyVariable Name="var2" Value="555"/>
   6:      <MyVariable Name="var3" Value="456"/>
   7:      <MyVariable Name="var4" Value="777"/>
   8:    </Variables>  
   9:  </Settings>

    Предположим, что нам нужно получить значение атрибута 'Value' элемента 'MyVariable' - того, у которого атрибут 'Name' имеет значение 'var2'. Запускаем AutoCAD, открываем чертёж и загружаем нашу библиотеку (командой NetLoad). В командной строке AutoCAD запускаем такое Lisp-выражение:

(hwd-XmlGetAttribValue "C:\\000\\Settings.xml" "Variables/MyVariable" "Name" "var2" "Value")

    Скрин работы нашей функции: 


Аналогично приведённому выше примеру, с помощью .Net можно написать множество других lisp-функций (было бы желание)...

P.S.

    В параметре "FilterValue" можно было бы реализовать передачу не значения, а любого выражения, возвращающего логический (True/False) результат, на основании которого выполнялась бы фильтрация (тогда параметр следовало бы назвать просто "Filter"). В этом случае можно было бы динамически создавать гибкие фильтры и к тому же обойтись без параметра "FilterAttributeName".

Исходный код примера здесь.

Comments