50 void move(
int dx,
int dy) {
m_x=
static_cast<uint32_t
>(
m_x+dx);
m_y=
static_cast<uint32_t
>(
m_y+dy); }
53 float xfPos()
const {
return static_cast<float>(
m_x); }
54 float yfPos()
const {
return static_cast<float>(
m_y); }
81 using Ptr = std::unique_ptr<DiagramItem>;
82 using Vec = std::vector<Ptr>;
105 using Ptr = std::unique_ptr<DiagramRow>;
106 using Vec = std::vector<Ptr>;
114 bool doBase,
bool bitmap,
115 uint32_t baseRows,uint32_t superRows,
116 uint32_t cellWidth,uint32_t cellHeight,
118 bool generateMap=
true);
120 bool doBase,
bool bitmap,
121 uint32_t baseRows,uint32_t superRows,
122 uint32_t cellWidth,uint32_t cellheight);
126 {
m_rows.push_back(std::make_unique<DiagramRow>(
this,l));
return m_rows.back().get(); }
151 case Protection::Public:
return 0xffffffff;
152 case Protection::Package:
153 case Protection::Protected:
return 0xcccccccc;
154 case Protection::Private:
return 0xaaaaaaaa;
163 case Protection::Public:
return 6;
164 case Protection::Package:
165 case Protection::Protected:
return 5;
166 case Protection::Private:
return 4;
175 case Protection::Public:
return "solid";
176 case Protection::Package:
177 case Protection::Protected:
return "dashed";
178 case Protection::Private:
return "dotted";
187 case Specifier::Normal:
return 0xffffffff;
188 case Specifier::Virtual:
return 0xf0f0f0f0;
196 if (s.
empty())
return s;
199 const char *p=s.
data();
205 case '(': result+=
"\\(";
break;
206 case ')': result+=
"\\)";
break;
207 default: result+=c;
break;
216 auto it = dil.begin();
220 result=(*it)->protection();
221 for (++it;it!=dil.end();++it)
226 if (result==Protection::Protected && p==Protection::Public) result=p;
227 else if (result==Protection::Private) result=p;
235 uint32_t x,uint32_t y,uint32_t w,uint32_t h,
bool firstRow,
236 bool hasDocs,
bool children=
false)
238 uint8_t colFill = hasDocs ? (firstRow ? 8 : 2) : 7;
239 uint8_t colBorder = (firstRow || !hasDocs) ? 1 : 3;
242 image->
fillRect(x+1,y+1,w-2,h-2,colFill,mask);
243 image->
drawRect(x,y,w,h,colBorder,mask);
247 for (uint32_t i=0;i<5;i++)
249 image->
drawHorzLine(y+h+i-6,x+w-2-i,x+w-2,firstRow?1:3,0xffffffff);
255 float x,
float y,
bool children=
false)
257 if (di->
virtualness()==Specifier::Virtual) t <<
"dashed\n";
259 if (children) t << x <<
" " << y <<
" mark\n";
260 if (di->
virtualness()==Specifier::Virtual) t <<
"solid\n";
264 uint32_t x,uint32_t y,uint32_t w,uint32_t h)
285 if (!tooltip.
empty())
290 t <<
"\" shape=\"rect\" coords=\"" << x <<
"," << y <<
",";
291 t << (x+w) <<
"," << (y+h) <<
"\"/>\n";
340 return static_cast<uint32_t
>(
m_children.size());
358 m_items.push_back(std::move(di));
366 if (count>0 && (prot!=Protection::Private || !doBases))
383 doBases ? bcd.virt : Specifier::Normal,
384 doBases ? bcd.templSpecifiers :
DString());
394 auto row = std::make_unique<DiagramRow>(
this,0);
397 row_ptr->
insertClass(
nullptr,root,doBases,Protection::Public,Specifier::Normal,
DString());
417 uint32_t pPos=root->
xPos();
424 for (uint32_t k=children.front()->number();k<row->numItems();k++)
435 for (uint32_t k=root->
number();k<row->numItems();k++)
443 auto it = children.begin();
444 for (;it!=children.end() && !moved && !(*it)->isInList();++it)
458 const auto &
row = *it;
463 for (
const auto &di : *
row)
466 if (pi==opi && !first) { delta-=
gridWidth; }
482 const auto &
row = *it;
490 while (rit!=
row->
end() && (*rit)->parentItem()==pi)
508 while (it!=
m_rows.end() && !(*it)->item(0)->isInList())
517 const auto &
row = *it;
518 uint32_t maxListLen=0;
519 uint32_t curListLen=0;
521 for (
const auto &di : *
row)
523 if (di->parentItem()!=opi) curListLen=1;
else curListLen++;
524 if (curListLen>maxListLen) maxListLen=curListLen;
536 for (
const auto &dr :
m_rows)
539 for (
const auto &di : *dr)
541 if (di->isInList()) done=
true;
542 if (maxXPos) mx=std::max(mx,di->xPos());
547 if (maxLabelLen) *maxLabelLen=ml;
548 if (maxXPos) *maxXPos=mx;
552template<
class C,
class I>
559 if (fwd)
m_it = container.begin();
560 else m_rit = container.rbegin();
587 bool doBase,
bool bitmap,
588 uint32_t baseRows,uint32_t superRows,
589 uint32_t cellWidth,uint32_t cellHeight,
594 if (it!=
m_rows.end() && !doBase) ++it;
595 bool firstRow = doBase;
597 float superRowsF =
static_cast<float>(superRows);
598 for (;it!=
m_rows.end() && !done;++it)
600 const auto &dr = *it;
602 float xf=0.0f,yf=0.0f;
620 if (doBase) yf += 1.0f;
632 superRows*cellHeight-
662 if (!firstRow && generateMap)
676 for (
const auto &di : *dr)
684 superRows*cellHeight-
693 bool hasDocs=di->getClassDef()->isLinkable();
694 writeBitmapBox(di.get(),image,x,y,cellWidth,cellHeight,firstRow,hasDocs);
695 if (!firstRow && generateMap)
696 writeMapArea(t,di->getClassDef(),relPath,x,y,cellWidth,cellHeight);
718 bool doBase,
bool bitmap,
719 uint32_t baseRows,uint32_t superRows,
720 uint32_t cellWidth,uint32_t cellHeight)
724 float superRowsF =
static_cast<float>(superRows);
725 for (;it!=
m_rows.end() && !done;++it)
727 const auto &dr = *it;
731 uint32_t x=0,y=0,ys=0;
732 float xf=0.0f,yf=0.0f,ysf=0.0f;
733 auto rit = dr->begin();
734 while (rit!=dr->end())
791 y = ys - cellHeight/2;
797 y = ys + cellHeight/2;
838 t <<
"1 " << xf <<
" " << yf <<
" hedge\n";
843 t <<
"0 " << xf <<
" " << yf <<
" hedge\n";
848 if (rit!=dr->end()) di = (*rit).get();
else di=
nullptr;
877 t <<
"1 " << xf <<
" " << yf <<
" hedge\n";
881 t <<
"0 " << xf <<
" " << yf <<
" hedge\n";
886 t << xf <<
" " << ysf <<
" " << yf <<
" vedge\n";
890 t << xf <<
" " << (ysf + 0.25f) <<
" " << yf <<
" vedge\n";
894 if (rit!=dr->end()) ++rit;
900 for (
const auto &di : *dr)
936 t <<
"1 " << di->xfPos()/
gridWidth <<
" "
937 << (di->yfPos()/
gridHeight+superRowsF-1.0f) <<
" in\n";
941 t <<
"0 " << di->xfPos()/
gridWidth <<
" "
976 t <<
"0 " << di->xfPos()/
gridWidth <<
" "
977 << (di->yfPos()/
gridHeight+superRowsF-1.0f) <<
" out\n";
981 t <<
"1 " << di->xfPos()/
gridWidth <<
" "
989 if (first!=last && !first->
isInList())
1045 p->base.computeLayout();
1046 p->super.computeLayout();
1049 uint32_t xbase = baseItem->
xPos();
1050 uint32_t xsuper = superItem->
xPos();
1053 int dist=
static_cast<int>(xbase-xsuper);
1054 superItem->
move(dist,0);
1055 p->super.moveChildren(superItem,dist);
1057 else if (xbase<xsuper)
1059 int dist=
static_cast<int>(xsuper-xbase);
1060 baseItem->
move(dist,0);
1061 p->base.moveChildren(baseItem,dist);
1068 const DString &fileName)
const
1070 uint32_t baseRows=
p->base.computeRows();
1071 uint32_t superRows=
p->super.computeRows();
1072 uint32_t baseMaxX = 0, baseMaxLabelWidth = 0, superMaxX = 0, superMaxLabelWidth = 0;
1073 p->base.computeExtremes(&baseMaxLabelWidth,&baseMaxX);
1074 p->super.computeExtremes(&superMaxLabelWidth,&superMaxX);
1076 uint32_t rows=std::max(1u,baseRows+superRows-1);
1080 float estHeight =
static_cast<float>(rows)*40.0f;
1081 float estWidth =
static_cast<float>(cols)*(20+
static_cast<float>(std::max(baseMaxLabelWidth,superMaxLabelWidth)));
1084 const float pageWidth = 14.0f;
1089 float realHeight =
static_cast<float>(std::min(rows,12u));
1090 float realWidth = realHeight * estWidth/estHeight;
1091 if (realWidth>pageWidth)
1093 realHeight*=pageWidth/realWidth;
1097 output <<
"\\begin{figure}[H]\n"
1100 output <<
"\\includegraphics[height=" << realHeight <<
"cm]{"
1101 << fileName <<
"}\n";
1102 output <<
"\\end{center}\n"
1108 DString epsName=epsBaseName+
".eps";
1112 term(
"Could not open file {} for writing\n",epsName);
1122 t <<
"%!PS-Adobe-2.0 EPSF-2.0\n";
1123 t <<
"%%Title: ClassName\n";
1124 t <<
"%%Creator: Doxygen\n";
1125 t <<
"%%CreationDate: Time\n";
1127 t <<
"%Magnification: 1.00\n";
1128 t <<
"%%Orientation: Portrait\n";
1129 t <<
"%%BoundingBox: 0 0 500 " << estHeight*500.0f/estWidth <<
"\n";
1130 t <<
"%%Pages: 0\n";
1131 t <<
"%%BeginSetup\n";
1132 t <<
"%%EndSetup\n";
1133 t <<
"%%EndComments\n";
1135 t <<
"% ----- variables -----\n";
1137 t <<
"/boxwidth 0 def\n";
1138 t <<
"/boxheight 40 def\n";
1139 t <<
"/fontheight 24 def\n";
1140 t <<
"/marginwidth 10 def\n";
1141 t <<
"/distx 20 def\n";
1142 t <<
"/disty 40 def\n";
1143 t <<
"/boundaspect " << estWidth/estHeight <<
" def % aspect ratio of the BoundingBox (width/height)\n";
1144 t <<
"/boundx 500 def\n";
1145 t <<
"/boundy boundx boundaspect div def\n";
1146 t <<
"/xspacing 0 def\n";
1147 t <<
"/yspacing 0 def\n";
1148 t <<
"/rows " << rows <<
" def\n";
1149 t <<
"/cols " << cols <<
" def\n";
1150 t <<
"/scalefactor 0 def\n";
1151 t <<
"/boxfont /Times-Roman findfont fontheight scalefont def\n";
1153 t <<
"% ----- procedures -----\n";
1155 t <<
"/dotted { [1 4] 0 setdash } def\n";
1156 t <<
"/dashed { [5] 0 setdash } def\n";
1157 t <<
"/solid { [] 0 setdash } def\n";
1159 t <<
"/max % result = MAX(arg1,arg2)\n";
1161 t <<
" /a exch def\n";
1162 t <<
" /b exch def\n";
1163 t <<
" a b gt {a} {b} ifelse\n";
1166 t <<
"/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2)\n";
1168 t <<
" 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max\n";
1171 t <<
"/cw % boxwidth = MAX(boxwidth, stringwidth(arg1))\n";
1173 t <<
" /str exch def\n";
1174 t <<
" /boxwidth boxwidth str stringwidth pop max def\n";
1177 t <<
"/box % draws a box with text 'arg1' at grid pos (arg2,arg3)\n";
1179 t <<
" 2 setlinewidth\n";
1181 t <<
" exch xspacing mul xoffset add\n";
1182 t <<
" exch yspacing mul\n";
1184 t <<
" boxwidth 0 rlineto \n";
1185 t <<
" 0 boxheight rlineto \n";
1186 t <<
" boxwidth neg 0 rlineto \n";
1187 t <<
" 0 boxheight neg rlineto \n";
1188 t <<
" closepath\n";
1189 t <<
" dup stringwidth pop neg boxwidth add 2 div\n";
1190 t <<
" boxheight fontheight 2 div sub 2 div\n";
1191 t <<
" rmoveto show stroke\n";
1197 t <<
" exch xspacing mul xoffset add boxwidth add\n";
1198 t <<
" exch yspacing mul\n";
1200 t <<
" 0 boxheight 4 div rlineto\n";
1201 t <<
" boxheight neg 4 div boxheight neg 4 div rlineto\n";
1202 t <<
" closepath\n";
1210 t <<
" 3 -8 rlineto\n";
1211 t <<
" -6 0 rlineto\n";
1212 t <<
" 3 8 rlineto\n";
1213 t <<
" closepath\n";
1218 t <<
"/out % draws an output connector for the block at (arg1,arg2)\n";
1221 t <<
" exch xspacing mul xoffset add boxwidth 2 div add\n";
1222 t <<
" exch yspacing mul boxheight add\n";
1223 t <<
" /y exch def\n";
1224 t <<
" /x exch def\n";
1225 t <<
" x y moveto\n";
1226 t <<
" 0 disty 2 div rlineto \n";
1228 t <<
" 1 eq { x y disty 2 div add arrow } if\n";
1231 t <<
"/in % draws an input connector for the block at (arg1,arg2)\n";
1234 t <<
" exch xspacing mul xoffset add boxwidth 2 div add\n";
1235 t <<
" exch yspacing mul disty 2 div sub\n";
1236 t <<
" /y exch def\n";
1237 t <<
" /x exch def\n";
1238 t <<
" x y moveto\n";
1239 t <<
" 0 disty 2 div rlineto\n";
1241 t <<
" 1 eq { x y disty 2 div add arrow } if\n";
1246 t <<
" exch xspacing mul xoffset add boxwidth 2 div add\n";
1247 t <<
" exch yspacing mul boxheight 2 div sub\n";
1248 t <<
" /y exch def\n";
1249 t <<
" /x exch def\n";
1251 t <<
" x y moveto\n";
1252 t <<
" boxwidth 2 div distx add 0 rlineto\n";
1255 t <<
" { newpath x boxwidth 2 div distx add add y moveto\n";
1256 t <<
" -8 3 rlineto\n";
1257 t <<
" 0 -6 rlineto\n";
1258 t <<
" 8 3 rlineto\n";
1259 t <<
" closepath\n";
1267 t <<
" /ye exch def\n";
1268 t <<
" /ys exch def\n";
1269 t <<
" /xs exch def\n";
1271 t <<
" xs xspacing mul xoffset add boxwidth 2 div add dup\n";
1272 t <<
" ys yspacing mul boxheight 2 div sub\n";
1274 t <<
" ye yspacing mul boxheight 2 div sub\n";
1279 t <<
"/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3'\n";
1281 t <<
" /ys exch def\n";
1282 t <<
" /xe exch def\n";
1283 t <<
" /xs exch def\n";
1285 t <<
" xs xspacing mul xoffset add boxwidth 2 div add\n";
1286 t <<
" ys yspacing mul disty 2 div sub\n";
1288 t <<
" xspacing xe xs sub mul 0\n";
1293 t <<
"% ----- main ------\n";
1295 t <<
"boxfont setfont\n";
1296 t <<
"1 boundaspect scale\n";
1299 for (
const auto &dr :
p->base)
1302 for (
const auto &di : *dr)
1304 done=di->isInList();
1310 auto it =
p->super.begin();
1311 if (it!=
p->super.end()) ++it;
1312 for (;it!=
p->super.end();++it)
1314 const auto &dr = *it;
1316 for (
const auto &di : *dr)
1318 done=di->isInList();
1324 t <<
"/boxwidth boxwidth marginwidth 2 mul add def\n"
1325 <<
"/xspacing boxwidth distx add def\n"
1326 <<
"/yspacing boxheight disty add def\n"
1327 <<
"/scalefactor \n"
1328 <<
" boxwidth cols mul distx cols 1 sub mul add\n"
1329 <<
" boxheight rows mul disty rows 1 sub mul add boundaspect mul \n"
1331 <<
"boundx scalefactor div boundy scalefactor div scale\n";
1333 t <<
"\n% ----- classes -----\n\n";
1334 p->base.drawBoxes(t,
nullptr,
true,
false,baseRows,superRows,0,0);
1335 p->super.drawBoxes(t,
nullptr,
false,
false,baseRows,superRows,0,0);
1337 t <<
"\n% ----- relations -----\n\n";
1338 p->base.drawConnectors(t,
nullptr,
true,
false,baseRows,superRows,0,0);
1339 p->super.drawConnectors(t,
nullptr,
false,
false,baseRows,superRows,0,0);
1347 epstopdfArgs.
sprintf(
"\"%s.eps\" --outfile=\"%s.pdf\"",
1352 err(
"Problems running epstopdf. Check your TeX installation!\n");
1365 bool generateMap,
bool toIndex)
const
1367 uint32_t baseRows=
p->base.computeRows();
1368 uint32_t superRows=
p->super.computeRows();
1369 uint32_t rows=baseRows+superRows-1;
1371 uint32_t lb=0,ls=0,xb=0,xs=0;
1372 p->base.computeExtremes(&lb,&xb);
1373 p->super.computeExtremes(&ls,&xs);
1376 uint32_t maxXPos = std::max(xb,xs);
1377 uint32_t labelVertMargin = 6;
1378 uint32_t cellHeight = labelVertMargin*2+
fontHeight;
1383 Image image(imageWidth,imageHeight);
1385 p->base.drawBoxes(t,&image,
true,
true,baseRows,superRows,cellWidth,cellHeight,relPath,generateMap);
1386 p->super.drawBoxes(t,&image,
false,
true,baseRows,superRows,cellWidth,cellHeight,relPath,generateMap);
1387 p->base.drawConnectors(t,&image,
true,
true,baseRows,superRows,cellWidth,cellHeight);
1388 p->super.drawConnectors(t,&image,
false,
true,baseRows,superRows,cellWidth,cellHeight);
1390#define IMAGE_EXT ".png"
A abstract class representing of a compound symbol.
virtual bool isVisibleInHierarchy() const =0
the class is visible in a class diagram, or class hierarchy
virtual const BaseClassList & baseClasses() const =0
Returns the list of base classes from which this class directly inherits.
virtual const BaseClassList & subClasses() const =0
Returns the list of sub classes that directly derive from this class.
void writeImage(TextStream &t, const DString &path, const DString &relPath, const DString &file, bool generateMap, bool toIndex) const
void writeFigure(TextStream &t, const DString &path, const DString &file) const
ClassDiagram(const ClassDef *root)
std::unique_ptr< Private > p
A String class for use with Doxygen wrapping std::string and adding some additional functionality off...
bool empty() const
Returns true iff the string is empty (std::string compatible alias for isEmpty()).
DString & sprintf(const char *format,...)
void reserve(size_t size)
Reserve space for size bytes without changing the string contents.
DString left(size_t len) const
const std::string & str() const
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
bool endsWith(const char *s) const
size_t length() const
Returns the length of the string, not counting the 0-terminator.
virtual bool isLinkable() const =0
virtual const DString & name() const =0
virtual DString briefDescriptionAsTooltip() const =0
virtual DString displayName(bool includeScope=true) const =0
virtual DString anchor() const =0
virtual DString getReference() const =0
virtual DString getOutputFileBase() const =0
Class representing a single node in the built-in class diagram.
Specifier virtualness() const
void addChild(DiagramItem *di)
DiagramItemList getChildren()
DiagramItem(DiagramItem *p, uint32_t number, const ClassDef *cd, Protection prot, Specifier virt, const DString &ts)
const ClassDef * m_classDef
uint32_t avgChildPos() const
DiagramItem * parentItem()
const ClassDef * getClassDef() const
uint32_t numChildren() const
Protection protection() const
void move(int dx, int dy)
DiagramItemList m_children
Class representing a row in the built-in class diagram.
uint32_t numItems() const
std::unique_ptr< DiagramItem > Ptr
DiagramItem * item(int index)
typename Vec::reverse_iterator reverse_iterator
DiagramRow(TreeDiagram *d, uint32_t l)
typename Vec::iterator iterator
void insertClass(DiagramItem *parent, const ClassDef *cd, bool doBases, Protection prot, Specifier virt, const DString &ts)
reverse_iterator rbegin()
Class representing a directory in the file system.
bool remove(const std::string &path, bool acceptsAbsPath=true) const
static IndexList * indexList
helper class representing an iterator that can iterate forwards or backwards
C::reverse_iterator m_rit
DualDirIterator(C &container, bool fwd)
Class representing a bitmap image generated by doxygen.
void drawVertLine(uint32_t x, uint32_t ys, uint32_t ye, uint8_t colIndex, uint32_t mask)
void drawHorzLine(uint32_t y, uint32_t xs, uint32_t xe, uint8_t colIndex, uint32_t mask)
void drawVertArrow(uint32_t x, uint32_t ys, uint32_t ye, uint8_t colIndex, uint32_t mask)
void fillRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint8_t colIndex, uint32_t mask)
void drawHorzArrow(uint32_t y, uint32_t xs, uint32_t xe, uint8_t colIndex, uint32_t mask)
void drawRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint8_t colIndex, uint32_t mask)
void writeString(uint32_t x, uint32_t y, const DString &s, uint8_t fg)
friend uint32_t stringLength(const DString &s)
void addImageFile(const DString &name)
Text streaming class that buffers data.
Class representing the tree layout for the built-in class diagram.
void drawConnectors(TextStream &t, Image *image, bool doBase, bool bitmap, uint32_t baseRows, uint32_t superRows, uint32_t cellWidth, uint32_t cellheight)
void drawBoxes(TextStream &t, Image *image, bool doBase, bool bitmap, uint32_t baseRows, uint32_t superRows, uint32_t cellWidth, uint32_t cellHeight, DString relPath="", bool generateMap=true)
std::unique_ptr< DiagramRow > Ptr
bool layoutTree(DiagramItem *root, uint32_t row)
DiagramRow * row(int index)
void moveChildren(DiagramItem *root, int dx)
typename Vec::iterator iterator
TreeDiagram(const ClassDef *root, bool doBases)
DiagramRow * addRow(uint32_t l)
void computeExtremes(uint32_t *labelWidth, uint32_t *xpos)
#define Config_getBool(name)
const uint32_t maxTreeWidth
static DString convertToPSString(const DString &s)
const uint32_t labelHorMargin
static void writeVectorBox(TextStream &t, DiagramItem *di, float x, float y, bool children=false)
static DString protToString(Protection p)
static void writeBitmapBox(DiagramItem *di, Image *image, uint32_t x, uint32_t y, uint32_t w, uint32_t h, bool firstRow, bool hasDocs, bool children=false)
const uint32_t labelHorSpacing
std::vector< DiagramItem * > DiagramItemList
static void writeMapArea(TextStream &t, const ClassDef *cd, DString relPath, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
const uint32_t gridHeight
const uint32_t fontHeight
static Protection getMinProtectionLevel(const DiagramItemList &dil)
static uint32_t virtToMask(Specifier p)
static uint8_t protToColor(Protection p)
static uint32_t protToMask(Protection p)
const uint32_t labelVertSpacing
constexpr DocNodeVariant * parent(DocNodeVariant *n)
returns the parent node of a given node n or nullptr if the node has no parent.
const char * qPrint(const char *s)
int system(const DString &command, const DString &args, bool commandHasConsole=true)
std::ofstream openOutputStream(const DString &name, bool append=false)
Portable versions of functions that are platform dependent.
Private(const ClassDef *root)
DString externalLinkTarget(const bool parent)
DString insertTemplateSpecifierInScope(const DString &scope, const DString &templ)
DString stripScope(const DString &name)
DString convertToHtml(const DString &s, bool keepEntities)
void addHtmlExtensionIfMissing(DString &fName)
DString convertToXML(const DString &s, bool keepEntities, const bool citeEntry)
DString externalRef(const DString &relPath, const DString &ref)
A bunch of utility functions.