This post takes the code from the last post and extends it to serialize the collected RegAppId data to an XML file, as per Step 3 below:
- Implement a command to collect RegAppId information for the active document
- Extend this command to work on a drawing not loaded in the editor
- Save our RegAppId information to some persistent location (XML)
- Create a modified version of ScriptPro 2.0 (one of our Plugins of the Month) to call our command without opening the drawing
I’ve chosen once again to leverage the very handy XmlSerializer class to handle serialization to and from XML: all we have to do is get our data in a data structure with appropriate metadata and the rest is taken care of by .NET. More or less, anyway. :-)
Here’s the updated code, with the new/modified lines in red (and here’s the updated source file):
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.EditorInput;
4 using Autodesk.AutoCAD.Runtime;
5 using System.Collections.Generic;
6 using System.Text;
7 using System.Xml;
8 using System.Xml.Serialization;
9 using System.IO;
10 using System;
11
12 namespace XrefRegApps
13 {
14 [XmlRoot("RegAppData")]
15 public class RegAppData
16 {
17 // The name of our drawing
18
19 private string _name;
20
21 [XmlAttribute("Name")]
22 public string Name
23 {
24 get { return _name; }
25 set { _name = value; }
26 }
27
28 // The location of our drawing
29
30 private string _dwg;
31
32 [XmlAttribute("File")]
33 public string File
34 {
35 get { return _dwg; }
36 set { _dwg = value; }
37 }
38
39 // The list of our drawing's Registered Application IDs
40
41 private List<string> _regAppIds = new List<string>();
42
43 [XmlAttribute("RegAppIds")]
44 public List<string> RegAppIds
45 {
46 get { return _regAppIds; }
47 set { _regAppIds = value; }
48 }
49
50 public void AddRegAppId(string id)
51 {
52 if (!_regAppIds.Contains(id))
53 _regAppIds.Add(id);
54 }
55 }
56
57 public class Commands
58 {
59 private const string regAppDataXml = "RegAppData.xml";
60
61 private void SerializeRegAppData(
62 Dictionary<string, RegAppData> dict
63 )
64 {
65 if (dict.Count > 0)
66 {
67 // No direct way to serialize a dictionary to XML,
68 // so we'll create a list from it and serialize that
69
70 List<RegAppData> regData =
71 new List<RegAppData>(dict.Count);
72 foreach (KeyValuePair<string, RegAppData> kv in dict)
73 {
74 regData.Add(kv.Value);
75 }
76
77 // Serialize our list to the specified XML file
78
79 XmlSerializer xs =
80 new XmlSerializer(typeof(List<RegAppData>));
81 XmlTextWriter xw =
82 new XmlTextWriter(regAppDataXml, Encoding.UTF8);
83 xs.Serialize(xw, regData);
84 xw.Close();
85 }
86 }
87
88 // Read and return the previous Registered Application data
89 // from our stored XML file
90
91 private Dictionary<string, RegAppData>
92 DeserializeRegAppData()
93 {
94 if (File.Exists(regAppDataXml))
95 {
96 XmlSerializer xs =
97 new XmlSerializer(typeof(List<RegAppData>));
98 XmlTextReader xr = new XmlTextReader(regAppDataXml);
99 if (xs.CanDeserialize(xr))
100 {
101 // No direct way to serialize a dictionary to XML,
102 // so we'll read a list and populate a dictionary
103 // from it
104
105 List<RegAppData> regData =
106 (List<RegAppData>)xs.Deserialize(xr);
107 xr.Close();
108 Dictionary<string, RegAppData> dict =
109 new Dictionary<string, RegAppData>();
110 foreach (RegAppData rad in regData)
111 {
112 dict.Add(rad.File, rad);
113 }
114 return dict;
115 }
116 }
117 return new Dictionary<string, RegAppData>();
118 }
119
120 [CommandMethod("XRA")]
121 public void ExternalReferenceRegisteredApps()
122 {
123 Document doc =
124 Application.DocumentManager.MdiActiveDocument;
125 Database db = doc.Database;
126 Editor ed = doc.Editor;
127
128 // Prompt the user for the path to a DWG to test
129
130 PromptStringOptions pso =
131 new PromptStringOptions(
132 "\nEnter path to root drawing file: "
133 );
134 pso.AllowSpaces = true;
135 PromptResult pr = ed.GetString(pso);
136 if (pr.Status != PromptStatus.OK)
137 return;
138
139 if (!File.Exists(pr.StringResult))
140 {
141 ed.WriteMessage("\nFile does not exist.");
142 return;
143 }
144
145 try
146 {
147 // We start by reading any existing RegApp data
148 // from XML
149
150 Dictionary<string, RegAppData> regData =
151 DeserializeRegAppData();
152
153 // Load the DWG into a side database
154
155 Database mainDb = new Database(false, true);
156 using (mainDb)
157 {
158 mainDb.ReadDwgFile(
159 pr.StringResult,
160 FileOpenMode.OpenForReadAndAllShare,
161 true,
162 null
163 );
164
165 // We need an additional step to resolve Xrefs
166
167 mainDb.ResolveXrefs(false, false);
168
169 // Get the XrefGraph for the specified database
170
171 XrefGraph xg = mainDb.GetHostDwgXrefGraph(true);
172
173 // Loop through the nodes in the graph
174
175 for (int i = 0; i < xg.NumNodes; i++)
176 {
177 // Get each node and check its status
178
179 XrefGraphNode xgn = xg.GetXrefNode(i);
180
181 switch (xgn.XrefStatus)
182 {
183 // If Un*, print a message
184
185 case XrefStatus.Unresolved:
186 ed.WriteMessage(
187 "\nUnresolved xref \"{0}\"", xgn.Name
188 );
189 break;
190 case XrefStatus.Unloaded:
191 ed.WriteMessage(
192 "\nUnloaded xref \"{0}\"", xgn.Name
193 );
194 break;
195 case XrefStatus.Unreferenced:
196 ed.WriteMessage(
197 "\nUnreferenced xref \"{0}\"", xgn.Name
198 );
199 break;
200 case XrefStatus.Resolved:
201 {
202 // If Resolved, get the RegAppTable
203
204 Database xdb = xgn.Database;
205 if (xdb != null)
206 {
207 Transaction tr =
208 xdb.TransactionManager.StartTransaction();
209 using (tr)
210 {
211 RegAppTable rat =
212 (RegAppTable)tr.GetObject(
213 xdb.RegAppTableId,
214 OpenMode.ForRead
215 );
216
217 // We'll store our RegApp data in an
218 // object
219
220 RegAppData dwgData = new RegAppData();
221 dwgData.Name =
222 (i == 0 ?
223 Path.GetFileNameWithoutExtension(
224 xgn.Name
225 ) :
226 xgn.Name
227 );
228 dwgData.File = xdb.Filename;
229
230 // Collect the contained names of the
231 // RegAppTableRecords (the RegAppIds)
232 // in a list
233
234 foreach (ObjectId id in rat)
235 {
236 RegAppTableRecord ratr =
237 (RegAppTableRecord)tr.GetObject(
238 id,
239 OpenMode.ForRead
240 );
241 dwgData.AddRegAppId(ratr.Name);
242 }
243
244 // Store the object in our dictionary,
245 // replacing any existing item
246 // (keyed off the entire DWG path, not
247 // just the name)
248
249 if (regData.ContainsKey(dwgData.File))
250 regData.Remove(dwgData.File);
251 regData.Add(dwgData.File, dwgData);
252
253 // Print the drawing information
254 // and the RegAppId count
255
256 ed.WriteMessage(
257 "\nDrawing \"{0}\" (\"{1}\") with " +
258 "{2} RegAppIds",
259 dwgData.Name,
260 dwgData.File,
261 dwgData.RegAppIds.Count
262 );
263
264 // Even if only reading, commit the
265 // transaction (it's cheaper than
266 // aborting)
267
268 tr.Commit();
269 }
270 }
271 break;
272 }
273 }
274 }
275 // Save our data to XML
276
277 SerializeRegAppData(regData);
278 }
279 }
280 catch (System.Exception ex)
281 {
282 ed.WriteMessage(
283 "\nProblem reading/processing \"{0}\": {1}",
284 pr.StringResult, ex.Message
285 );
286 }
287 }
288 }
289 }
When we first run the XRA command with our Civil sample file, we see the contents of the XML file (which seems to be written to the current directory – we could easily hardcode another location, of course) are:
if we run the XRA command again, this time specifying a file from the Architectural sample:
Command: XRA
Enter path to root drawing file: C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\A-01.dwg
Drawing "A-01" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\A-01.dwg") with 21 RegAppIds
Drawing "Grid Plan" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\Res\Grid Plan.dwg") with 7 RegAppIds
Drawing "Wall Base" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\Res\Wall Base.dwg") with 11 RegAppIds
We will see the XML now contains additional entries:
This is really a reasonably simple way of storing our data: it should be trivial to add additional data to the XML, such as the version of the DWG, an entry holding the number of entries in the RegAppId list (although we do have the list itself).
In the next post we may move on to modifying ScriptPro 2.0, but we may take a quick detour and put together an XML stylesheet to visualize the data – let’s see next week.