As raised as a possibility at the end of the last post, I did choose to throw together a quick XSLT stylesheet to generate an HTML report of the XML data created by our XRA command.
To enable this I did make a few changes to our command implementation, which we’ll take a look at first.
Here’s the updated C# code, with 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 private string _dwgVersion;
40
41 [XmlAttribute("DwgVersion")]
42 public string DwgVersion
43 {
44 get { return _dwgVersion; }
45 set { _dwgVersion = value; }
46 }
47
48 // The list of our drawing's Registered Application IDs
49
50 private List<string> _regAppIds = new List<string>();
51
52 [XmlArrayItem("RegAppId")]
53 public List<string> RegAppIds
54 {
55 get { return _regAppIds; }
56 set { _regAppIds = value; }
57 }
58
59 public void AddRegAppId(string id)
60 {
61 if (!_regAppIds.Contains(id))
62 _regAppIds.Add(id);
63 }
64 }
65
66 public class Commands
67 {
68 private const string regAppDataXml = "c:\\RegAppData.xml";
69
70 private void SerializeRegAppData(
71 Dictionary<string, RegAppData> dict
72 )
73 {
74 if (dict.Count > 0)
75 {
76 // No direct way to serialize a dictionary to XML,
77 // so we'll create a list from it and serialize that
78
79 List<RegAppData> regData =
80 new List<RegAppData>(dict.Count);
81 foreach (KeyValuePair<string, RegAppData> kv in dict)
82 {
83 regData.Add(kv.Value);
84 }
85
86 // Serialize our list to the specified XML file
87
88 XmlSerializer xs =
89 new XmlSerializer(typeof(List<RegAppData>));
90 XmlTextWriter xw =
91 new XmlTextWriter(regAppDataXml, Encoding.UTF8);
92 xw.WriteProcessingInstruction(
93 "xml-stylesheet",
94 "type=\"text/xsl\" href=\"RegAppData.xslt\""
95 );
96 xs.Serialize(xw, regData);
97 xw.Close();
98 }
99 }
100
101 // Read and return the previous Registered Application data
102 // from our stored XML file
103
104 private Dictionary<string, RegAppData>
105 DeserializeRegAppData()
106 {
107 if (File.Exists(regAppDataXml))
108 {
109 XmlSerializer xs =
110 new XmlSerializer(typeof(List<RegAppData>));
111 XmlTextReader xr = new XmlTextReader(regAppDataXml);
112 if (xs.CanDeserialize(xr))
113 {
114 // No direct way to serialize a dictionary to XML,
115 // so we'll read a list and populate a dictionary
116 // from it
117
118 List<RegAppData> regData =
119 (List<RegAppData>)xs.Deserialize(xr);
120 xr.Close();
121 Dictionary<string, RegAppData> dict =
122 new Dictionary<string, RegAppData>();
123 foreach (RegAppData rad in regData)
124 {
125 dict.Add(rad.File, rad);
126 }
127 return dict;
128 }
129 }
130 return new Dictionary<string, RegAppData>();
131 }
132
133 [CommandMethod("XRA")]
134 public void ExternalReferenceRegisteredApps()
135 {
136 Document doc =
137 Application.DocumentManager.MdiActiveDocument;
138 Database db = doc.Database;
139 Editor ed = doc.Editor;
140
141 // Prompt the user for the path to a DWG to test
142
143 PromptStringOptions pso =
144 new PromptStringOptions(
145 "\nEnter path to root drawing file: "
146 );
147 pso.AllowSpaces = true;
148 PromptResult pr = ed.GetString(pso);
149 if (pr.Status != PromptStatus.OK)
150 return;
151
152 if (!File.Exists(pr.StringResult))
153 {
154 ed.WriteMessage("\nFile does not exist.");
155 return;
156 }
157
158 try
159 {
160 // We start by reading any existing RegApp data
161 // from XML
162
163 Dictionary<string, RegAppData> regData =
164 DeserializeRegAppData();
165
166 // Load the DWG into a side database
167
168 Database mainDb = new Database(false, true);
169 using (mainDb)
170 {
171 mainDb.ReadDwgFile(
172 pr.StringResult,
173 FileOpenMode.OpenForReadAndAllShare,
174 true,
175 null
176 );
177
178 // We need an additional step to resolve Xrefs
179
180 mainDb.ResolveXrefs(false, false);
181
182 // Get the XrefGraph for the specified database
183
184 XrefGraph xg = mainDb.GetHostDwgXrefGraph(true);
185
186 // Loop through the nodes in the graph
187
188 for (int i = 0; i < xg.NumNodes; i++)
189 {
190 // Get each node and check its status
191
192 XrefGraphNode xgn = xg.GetXrefNode(i);
193
194 switch (xgn.XrefStatus)
195 {
196 // If Un*, print a message
197
198 case XrefStatus.Unresolved:
199 ed.WriteMessage(
200 "\nUnresolved xref \"{0}\"", xgn.Name
201 );
202 break;
203 case XrefStatus.Unloaded:
204 ed.WriteMessage(
205 "\nUnloaded xref \"{0}\"", xgn.Name
206 );
207 break;
208 case XrefStatus.Unreferenced:
209 ed.WriteMessage(
210 "\nUnreferenced xref \"{0}\"", xgn.Name
211 );
212 break;
213 case XrefStatus.Resolved:
214 {
215 // If Resolved, get the RegAppTable
216
217 Database xdb = xgn.Database;
218 if (xdb != null)
219 {
220 Transaction tr =
221 xdb.TransactionManager.StartTransaction();
222 using (tr)
223 {
224 RegAppTable rat =
225 (RegAppTable)tr.GetObject(
226 xdb.RegAppTableId,
227 OpenMode.ForRead
228 );
229
230 // We'll store our RegApp data in an
231 // object
232
233 RegAppData dwgData = new RegAppData();
234 dwgData.Name =
235 (i == 0 ?
236 Path.GetFileNameWithoutExtension(
237 xgn.Name
238 ) :
239 xgn.Name
240 );
241 dwgData.File = xdb.Filename;
242 dwgData.DwgVersion =
243 xdb.OriginalFileVersion.ToString();
244
245 // Collect the contained names of the
246 // RegAppTableRecords (the RegAppIds)
247 // in a list
248
249 foreach (ObjectId id in rat)
250 {
251 RegAppTableRecord ratr =
252 (RegAppTableRecord)tr.GetObject(
253 id,
254 OpenMode.ForRead
255 );
256 dwgData.AddRegAppId(ratr.Name);
257 }
258
259 // Store the object in our dictionary,
260 // replacing any existing item
261 // (keyed off the entire DWG path, not
262 // just the name)
263
264 if (regData.ContainsKey(dwgData.File))
265 regData.Remove(dwgData.File);
266 regData.Add(dwgData.File, dwgData);
267
268 // Print the drawing information
269 // and the RegAppId count
270
271 ed.WriteMessage(
272 "\nDrawing \"{0}\" (\"{1}\") with " +
273 "{2} RegAppIds",
274 dwgData.Name,
275 dwgData.File,
276 dwgData.RegAppIds.Count
277 );
278
279 // Even if only reading, commit the
280 // transaction (it's cheaper than
281 // aborting)
282
283 tr.Commit();
284 }
285 }
286 break;
287 }
288 }
289 }
290 // Save our data to XML
291
292 SerializeRegAppData(regData);
293 }
294 }
295 catch (System.Exception ex)
296 {
297 ed.WriteMessage(
298 "\nProblem reading/processing \"{0}\": {1}",
299 pr.StringResult, ex.Message
300 );
301 }
302 }
303 }
304 }
The changes are very minor: we’ve added a DwgVersion attribute to our XML, as well as adjusting the save type of RegAppIds from being a simple string attribute to an array of elements (which allows us to determine the number of RegAppIds per drawing more easily).
We've hardcoded the path of the XML file to C:\ (feel free to change this, of course), as well as writing a stylesheet reference directly into the XML to just let the browser take care of the transformation to HTML.
Speaking of the stylesheet, here’s the RegAppData.xslt file to be placed alongside the XML output (currently in C:\, as just mentioned):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="html" indent="yes" version="4.0"/>
<xsl:template match="ArrayOfRegAppData">
<h2>Registered application IDs per drawing</h2>
<table>
<tr>
<td><em>Drawing name</em></td>
<td>
<em>Number of RegAppIds</em>
</td>
</tr>
<xsl:apply-templates select="*"/>
</table>
</xsl:template>
<xsl:template match="RegAppData">
<tr>
<td>
<b>
<xsl:attribute name="Title">
<xsl:value-of select="@File"/>
<xsl:text> (</xsl:text>
<xsl:value-of select="@DwgVersion"/>
<xsl:text>)</xsl:text>
</xsl:attribute>
<xsl:value-of select="@Name"/>
</b>
</td>
<td>
<xsl:attribute name="Title">
<xsl:for-each select="RegAppIds/RegAppId">
<xsl:value-of select="node()"/>
<xsl:if test="position() != last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
<center>
<xsl:value-of select="count(RegAppIds/RegAppId)"/>
</center>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
The stylesheet is also quite simple: it creates an HTML file containing a table of two columns – one with the names of our drawings (with the path and version in a tooltip) and the number of RegAppIds found in that drawing (with the specific names in a tooltip). We’ll see that in action a bit.
Running the XRA command just as we did in the last post, we should see this kind of XML output:
<?xml-stylesheet type="text/xsl" href="RegAppData.xslt"?>
<ArrayOfRegAppData
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<RegAppData
Name="A-01"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\A-01.dwg"
DwgVersion="Current">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>CONTENTBLOCKDESCRIPTION</RegAppId>
<RegAppId>CONTENTBLOCKICON</RegAppId>
<RegAppId>CONTENTTABDATA</RegAppId>
<RegAppId>CONTENTCURRENTTAB</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>PE_URL</RegAppId>
<RegAppId>RAK</RegAppId>
<RegAppId>AcAecLayerStandard</RegAppId>
<RegAppId>ACAD_DSTYLE_DIMTEXT_FILL</RegAppId>
<RegAppId>ACAD_EXEMPT_FROM_CAD_STANDARDS</RegAppId>
<RegAppId>AcDbBlockRepETag</RegAppId>
<RegAppId>AcDbDynamicBlockTrueName</RegAppId>
<RegAppId>AcDbDynamicBlockGUID</RegAppId>
<RegAppId>AcDbBlockRepBTag</RegAppId>
<RegAppId>ACAD_DSTYLE_DIMEXT_LENGTH</RegAppId>
<RegAppId>ACAD_DSTYLE_DIMEXT_ENABLED</RegAppId>
<RegAppId>ACAD_DSTYLE_DIM_LINETYPE</RegAppId>
<RegAppId>ACAD_DSTYLE_DIM_EXT1_LINETYPE</RegAppId>
<RegAppId>ACAD_DSTYLE_DIM_EXT2_LINETYPE</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="Grid Plan"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\Res\Grid Plan.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>CONTENTBLOCKDESCRIPTION</RegAppId>
<RegAppId>CONTENTBLOCKICON</RegAppId>
<RegAppId>CONTENTTABDATA</RegAppId>
<RegAppId>CONTENTCURRENTTAB</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>ACAD_DSTYLE_DIMTEXT_FILL</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="Wall Base"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Architectural\Res\Wall Base.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>CONTENTBLOCKDESCRIPTION</RegAppId>
<RegAppId>CONTENTBLOCKICON</RegAppId>
<RegAppId>CONTENTTABDATA</RegAppId>
<RegAppId>CONTENTCURRENTTAB</RegAppId>
<RegAppId>RAK</RegAppId>
<RegAppId>AcAecLayerStandard</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>PE_URL</RegAppId>
<RegAppId>ACAD_DSTYLE_DIMTEXT_FILL</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="Master Site Plan"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\Master Site Plan.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>RAK</RegAppId>
<RegAppId>DCA_FIGURE_XENT</RegAppId>
<RegAppId>ADE</RegAppId>
<RegAppId>ALIGNMENT_DB_APP</RegAppId>
<RegAppId>SDI_PROFILE</RegAppId>
<RegAppId>SDSK_POINT</RegAppId>
<RegAppId>SVPLINE</RegAppId>
<RegAppId>ADCADD_ZZ</RegAppId>
<RegAppId>SDSK_PMN</RegAppId>
<RegAppId>CIVIL_DRAFT</RegAppId>
<RegAppId>CIVIL_LINE_TABLE</RegAppId>
<RegAppId>CIVIL_CURVE_TABLE</RegAppId>
<RegAppId>CIVIL_SPIRAL_TABLE</RegAppId>
<RegAppId>SDI_XSECTIONS</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
<RegAppId>DCO15</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="PB-BASE"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-BASE.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>RAK</RegAppId>
<RegAppId>DCA_FIGURE_XENT</RegAppId>
<RegAppId>ALIGNMENT_DB_APP</RegAppId>
<RegAppId>SDI_PROFILE</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="PB-EX41"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-EX41.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>SDSK_POINT</RegAppId>
<RegAppId>SVPLINE</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>DCO15</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
</RegAppIds>
</RegAppData>
<RegAppData
Name="PB-EX61"
File="C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-EX61.dwg"
DwgVersion="AC1021">
<RegAppIds>
<RegAppId>ACAD</RegAppId>
<RegAppId>SVPLINE</RegAppId>
<RegAppId>ACAD_PSEXT</RegAppId>
<RegAppId>DCO15</RegAppId>
<RegAppId>ACAD_MLEADERVER</RegAppId>
</RegAppIds>
</RegAppData>
</ArrayOfRegAppData>
Which, when loaded into a browser, should be displayed like this:
As we hover over the name of a drawing in which we’re interested, we see its path and drawing version:
And as we hover over the RegAppId count, we see the names of the individual IDs:
That’s it for this post. We’ll wrap this series up by making some modifications to ScriptPro 2.0 in the next post.