Imported Upstream version 1.9.8
[platform/upstream/doxygen.git] / src / dotfilepatcher.cpp
index 5f14fe1..f64268b 100644 (file)
@@ -13,8 +13,6 @@
 *
 */
 
-#include <sstream>
-
 #include "dotfilepatcher.h"
 #include "dotrunner.h"
 #include "config.h"
 #include "util.h"
 #include "dot.h"
 #include "dir.h"
+#include "portable.h"
+
+// top part of the interactive SVG header
+static const char svgZoomHeader1[] = R"svg(
+<svg id="main" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" onload="init(evt)">
+<style type="text/css"><![CDATA[
+.node, .edge {opacity: 0.7;}
+.node.selected, .edge.selected {opacity: 1;}
+.edge:hover path { stroke: red; }
+.edge:hover polygon { stroke: red; fill: red; }
+]]></style>
+)svg";
+
+// conditional part of the interactive SVG header in case the navigation panel is shown
+static const char svgZoomHeader2[] = R"svg(
+<defs>
+  <circle id="rim" cx="0" cy="0" r="7"/>
+  <circle id="rim2" cx="0" cy="0" r="3.5"/>
+  <g id="zoomPlus">
+    <use xlink:href="#rim" fill="#404040"><set attributeName="fill" to="#808080" begin="zoomplus.mouseover" end="zoomplus.mouseout"/></use>
+    <path d="M-4,0h8M0,-4v8" fill="none" stroke="white" stroke-width="1.5" pointer-events="none"/>
+  </g>
+  <g id="zoomMin">
+    <use xlink:href="#rim" fill="#404040"><set attributeName="fill" to="#808080" begin="zoomminus.mouseover" end="zoomminus.mouseout"/></use>
+    <path d="M-4,0h8" fill="none" stroke="white" stroke-width="1.5" pointer-events="none"/>
+  </g>
+  <g id="arrowUp" transform="translate(30 24)">
+    <use xlink:href="#rim"/>
+    <path pointer-events="none" fill="none" stroke="white" stroke-width="1.5" d="M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5"/>
+  </g>
+  <g id="arrowRight" transform="rotate(90) translate(36 -43)">
+    <use xlink:href="#rim"/>
+    <path pointer-events="none" fill="none" stroke="white" stroke-width="1.5" d="M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5"/>
+  </g>
+  <g id="arrowDown" transform="rotate(180) translate(-30 -48)">
+    <use xlink:href="#rim"/>
+    <path pointer-events="none" fill="none" stroke="white" stroke-width="1.5" d="M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5"/>
+  </g>
+  <g id="arrowLeft" transform="rotate(270) translate(-36 17)">
+    <use xlink:href="#rim"/>
+    <path pointer-events="none" fill="none" stroke="white" stroke-width="1.5" d="M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5"/>
+  </g>
+  <g id="resetDef">
+    <use xlink:href="#rim2" fill="#404040"><set attributeName="fill" to="#808080" begin="reset.mouseover" end="reset.mouseout"/></use>
+  </g>
+</defs>
+)svg";
 
-static const char svgZoomHeader[] =
-"<svg id=\"main\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" onload=\"init(evt)\">\n"
-"<style type=\"text/css\"><![CDATA[\n"
-".edge:hover path { stroke: red; }\n"
-".edge:hover polygon { stroke: red; fill: red; }\n"
-"]]></style>\n"
-"<script type=\"text/javascript\"><![CDATA[\n"
-"var edges = document.getElementsByTagName('g');\n"
-"if (edges && edges.length) {\n"
-"  for (var i=0;i<edges.length;i++) {\n"
-"    if (edges[i].id.substr(0,4)=='edge') {\n"
-"      edges[i].setAttribute('class','edge');\n"
-"    }\n"
-"  }\n"
-"}\n"
-"]]></script>\n"
-"        <defs>\n"
-"                <circle id=\"rim\" cx=\"0\" cy=\"0\" r=\"7\"/>\n"
-"                <circle id=\"rim2\" cx=\"0\" cy=\"0\" r=\"3.5\"/>\n"
-"                <g id=\"zoomPlus\">\n"
-"                        <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                                <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomplus.mouseover\" end=\"zoomplus.mouseout\"/>\n"
-"                        </use>\n"
-"                        <path d=\"M-4,0h8M0,-4v8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
-"                </g>\n"
-"                <g id=\"zoomMin\">\n"
-"                        <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                                <set attributeName=\"fill\" to=\"#808080\" begin=\"zoomminus.mouseover\" end=\"zoomminus.mouseout\"/>\n"
-"                        </use>\n"
-"                        <path d=\"M-4,0h8\" fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" pointer-events=\"none\"/>\n"
-"                </g>\n"
-"                <g id=\"dirArrow\">\n"
-"                        <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
-"                </g>\n"
-"               <g id=\"resetDef\">\n"
-"                       <use xlink:href=\"#rim2\" fill=\"#404040\">\n"
-"                               <set attributeName=\"fill\" to=\"#808080\" begin=\"reset.mouseover\" end=\"reset.mouseout\"/>\n"
-"                       </use>\n"
-"               </g>\n"
-"        </defs>\n"
-"\n"
-"<script type=\"text/javascript\">\n"
-;
+// part of the footer that is needed if the navigation panel is shown
+static const char svgZoomFooter1[] = R"svg(
+<g id="navigator" transform="translate(0 0)" fill="#404254">
+  <rect fill="#f2f5e9" fill-opacity="0.5" stroke="#606060" stroke-width=".5" x="0" y="0" width="60" height="60"/>
+  <use id="zoomplus" xlink:href="#zoomPlus" x="17" y="9" onmousedown="handleZoom(evt,'in')"/>
+  <use id="zoomminus" xlink:href="#zoomMin" x="42" y="9" onmousedown="handleZoom(evt,'out')"/>
+  <use id="reset" xlink:href="#resetDef" x="30" y="36" onmousedown="handleReset()"/>
+   <use id="arrowup" xlink:href="#arrowUp" x="0" y="0" onmousedown="handlePan(0,-1)"/>
+  <use id="arrowright" xlink:href="#arrowRight" x="0" y="0" onmousedown="handlePan(1,0)"/>
+  <use id="arrowdown" xlink:href="#arrowDown" x="0" y="0" onmousedown="handlePan(0,1)"/>
+  <use id="arrowleft" xlink:href="#arrowLeft" x="0" y="0" onmousedown="handlePan(-1,0)"/>
+</g>
+<svg viewBox="0 0 15 15" width="100%" height="30px" preserveAspectRatio="xMaxYMin meet">
+ <g id="arrow_out" transform="scale(0.3 0.3)">
+  <a xlink:href="$orgname" target="_base">
+   <rect id="button" ry="5" rx="5" y="6" x="6" height="38" width="38"
+        fill="#f2f5e9" fill-opacity="0.5" stroke="#606060" stroke-width="1.0"/>
+   <path id="arrow"
+     d="M 11.500037,31.436501 C 11.940474,20.09759 22.043105,11.32322 32.158766,21.979434 L 37.068811,17.246167 C 37.068811,17.246167 37.088388,32 37.088388,32 L 22.160133,31.978069 C 22.160133,31.978069 26.997745,27.140456 26.997745,27.140456 C 18.528582,18.264221 13.291696,25.230495 11.500037,31.436501 z"
+     style="fill:#404040;"/>
+  </a>
+ </g>
+</svg>
+)svg";
 
-static const char svgZoomFooter[] =
-// navigation panel
-"        <g id=\"navigator\" transform=\"translate(0 0)\" fill=\"#404254\">\n"
-"                <rect fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\".5\" x=\"0\" y=\"0\" width=\"60\" height=\"60\"/>\n"
-// zoom in
-"                <use id=\"zoomplus\" xlink:href=\"#zoomPlus\" x=\"17\" y=\"9\" onmousedown=\"handleZoom(evt,'in')\"/>\n"
-// zoom out
-"                <use id=\"zoomminus\" xlink:href=\"#zoomMin\" x=\"42\" y=\"9\" onmousedown=\"handleZoom(evt,'out')\"/>\n"
-// reset zoom
-"                <use id=\"reset\" xlink:href=\"#resetDef\" x=\"30\" y=\"36\" onmousedown=\"handleReset()\"/>\n"
-// arrow up
-"                <g id=\"arrowUp\" xlink:href=\"#dirArrow\" transform=\"translate(30 24)\" onmousedown=\"handlePan(0,-1)\">\n"
-"                  <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                        <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowUp.mouseover\" end=\"arrowUp.mouseout\"/>\n"
-"                  </use>\n"
-"                  <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
-"                </g>\n"
-// arrow right
-"                <g id=\"arrowRight\" xlink:href=\"#dirArrow\" transform=\"rotate(90) translate(36 -43)\" onmousedown=\"handlePan(1,0)\">\n"
-"                  <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                        <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowRight.mouseover\" end=\"arrowRight.mouseout\"/>\n"
-"                  </use>\n"
-"                  <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
-"                </g>\n"
-// arrow down
-"                <g id=\"arrowDown\" xlink:href=\"#dirArrow\" transform=\"rotate(180) translate(-30 -48)\" onmousedown=\"handlePan(0,1)\">\n"
-"                  <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                        <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowDown.mouseover\" end=\"arrowDown.mouseout\"/>\n"
-"                  </use>\n"
-"                  <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
-"                </g>\n"
-// arrow left
-"                <g id=\"arrowLeft\" xlink:href=\"#dirArrow\" transform=\"rotate(270) translate(-36 17)\" onmousedown=\"handlePan(-1,0)\">\n"
-"                  <use xlink:href=\"#rim\" fill=\"#404040\">\n"
-"                        <set attributeName=\"fill\" to=\"#808080\" begin=\"arrowLeft.mouseover\" end=\"arrowLeft.mouseout\"/>\n"
-"                  </use>\n"
-"                  <path fill=\"none\" stroke=\"white\" stroke-width=\"1.5\" d=\"M0,-3.0v7 M-2.5,-0.5L0,-3.0L2.5,-0.5\"/>\n"
-"                </g>\n"
-"        </g>\n"
-// link to original SVG
-"        <svg viewBox=\"0 0 15 15\" width=\"100%\" height=\"30px\" preserveAspectRatio=\"xMaxYMin meet\">\n"
-"         <g id=\"arrow_out\" transform=\"scale(0.3 0.3)\">\n"
-"          <a xlink:href=\"$orgname\" target=\"_base\">\n"
-"           <rect id=\"button\" ry=\"5\" rx=\"5\" y=\"6\" x=\"6\" height=\"38\" width=\"38\"\n"
-"                fill=\"#f2f5e9\" fill-opacity=\"0.5\" stroke=\"#606060\" stroke-width=\"1.0\"/>\n"
-"           <path id=\"arrow\"\n"
-"             d=\"M 11.500037,31.436501 C 11.940474,20.09759 22.043105,11.32322 32.158766,21.979434 L 37.068811,17.246167 C 37.068811,17.246167 37.088388,32 37.088388,32 L 22.160133,31.978069 C 22.160133,31.978069 26.997745,27.140456 26.997745,27.140456 C 18.528582,18.264221 13.291696,25.230495 11.500037,31.436501 z\"\n"
-"             style=\"fill:#404040;\"/>\n"
-"          </a>\n"
-"         </g>\n"
-"        </svg>\n"
-"</svg>\n"
-;
+// generic part of the interactive SVG footer
+static const char svgZoomFooter2[] = R"svg(
+<style type='text/css'>
+<![CDATA[
+[data-mouse-over-selected='false'] { opacity: 0.7; }
+[data-mouse-over-selected='true']  { opacity: 1.0; }
+]]>
+</style>
+<script type="application/ecmascript"><![CDATA[
+document.addEventListener('DOMContentLoaded', (event) => {
+  highlightEdges();
+  highlightAdjacentNodes();
+});
+]]></script>
+</svg>
+)svg";
 
 static QCString replaceRef(const QCString &buf,const QCString &relPath,
   bool urlOnly,const QCString &context,const QCString &target=QCString())
@@ -156,9 +145,15 @@ static QCString replaceRef(const QCString &buf,const QCString &relPath,
         const DocRef *df = std::get_if<DocRef>(&dfAstImpl->root);
         result+=externalRef(relPath,df->ref(),TRUE);
         if (!df->file().isEmpty())
-          result += addHtmlExtensionIfMissing(df->file());
+        {
+          QCString fn = df->file();
+          addHtmlExtensionIfMissing(fn);
+          result += fn;
+        }
         if (!df->anchor().isEmpty())
+        {
           result += "#" + df->anchor();
+        }
         result += "\"";
       }
       else
@@ -218,7 +213,7 @@ bool DotFilePatcher::convertMapFile(TextStream &t,const QCString &mapName,
                     const QCString &relPath, bool urlOnly,
                     const QCString &context)
 {
-  std::ifstream f(mapName.str(),std::ifstream::in);
+  std::ifstream f = Portable::openInputStream(mapName);
   if (!f.is_open())
   {
     err("problems opening map file %s for inclusion in the docs!\n"
@@ -233,6 +228,14 @@ bool DotFilePatcher::convertMapFile(TextStream &t,const QCString &mapName,
     if (buf.startsWith("<area"))
     {
       QCString replBuf = replaceRef(buf,relPath,urlOnly,context);
+      // in dot version 7.0.2 the alt attribute is, incorrectly, removed.
+      // see https://gitlab.com/graphviz/graphviz/-/issues/265
+      int indexA = replBuf.find("alt=");
+      if (indexA == -1)
+      {
+        replBuf = replBuf.left(5) + " alt=\"\"" + replBuf.right(replBuf.length() - 5);
+      }
+
       // strip id="..." from replBuf since the id's are not needed and not unique.
       int indexS = replBuf.find("id=\""), indexE;
       if (indexS>0 && (indexE=replBuf.find('"',indexS+4))!=-1)
@@ -295,47 +298,45 @@ int DotFilePatcher::addSVGObject(const QCString &baseName,
 bool DotFilePatcher::run() const
 {
   //printf("DotFilePatcher::run(): %s\n",qPrint(m_patchFile));
-  bool interactiveSVG_local = Config_getBool(INTERACTIVE_SVG);
+  bool interactiveSVG = Config_getBool(INTERACTIVE_SVG);
   bool isSVGFile = m_patchFile.endsWith(".svg");
   int graphId = -1;
   QCString relPath;
   if (isSVGFile)
   {
     const Map &map = m_maps.front(); // there is only one 'map' for a SVG file
-    interactiveSVG_local = interactiveSVG_local && map.zoomable;
+    interactiveSVG = interactiveSVG && map.zoomable;
     graphId = map.graphId;
     relPath = map.relPath;
     //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n",
     //    qPrint(m_patchFile),map->zoomable);
   }
-  std::string tmpName = m_patchFile.str()+".tmp";
-  std::string patchFile = m_patchFile.str();
+  QCString tmpName = m_patchFile+".tmp";
   Dir thisDir;
-  if (!thisDir.rename(patchFile,tmpName))
+  if (!thisDir.rename(m_patchFile.str(),tmpName.str()))
   {
-    err("Failed to rename file %s to %s!\n",qPrint(m_patchFile),tmpName.c_str());
+    err("Failed to rename file %s to %s!\n",qPrint(m_patchFile),qPrint(tmpName));
     return FALSE;
   }
-  std::ifstream fi(tmpName, std::ifstream::in);
-  std::ofstream fo(patchFile, std::ofstream::out | std::ofstream::binary);
+  std::ifstream fi = Portable::openInputStream(tmpName);
+  std::ofstream fo = Portable::openOutputStream(m_patchFile);
   if (!fi.is_open())
   {
-    err("problem opening file %s for patching!\n",tmpName.c_str());
-    thisDir.rename(tmpName,patchFile);
+    err("problem opening file %s for patching!\n",qPrint(tmpName));
+    thisDir.rename(tmpName.str(),m_patchFile.str());
     return FALSE;
   }
   if (!fo.is_open())
   {
     err("problem opening file %s for patching!\n",qPrint(m_patchFile));
-    thisDir.rename(tmpName,patchFile);
+    thisDir.rename(tmpName.str(),m_patchFile.str());
     return FALSE;
   }
   TextStream t(&fo);
-  int width,height;
+  int width=0,height=0;
   bool insideHeader=FALSE;
   bool replacedHeader=FALSE;
-  bool foundSize=FALSE;
-  int lineNr=1;
+  bool useNagivation=FALSE;
   std::string lineStr;
   static const reg::Ex reSVG(R"([\[<]!-- SVG [0-9]+)");
   static const reg::Ex reMAP(R"(<!-- MAP [0-9]+)");
@@ -348,23 +349,31 @@ bool DotFilePatcher::run() const
     int i;
     if (isSVGFile)
     {
-      if (interactiveSVG_local)
+      if (interactiveSVG)
       {
         if (line.find("<svg")!=-1 && !replacedHeader)
         {
           int count;
           count = sscanf(line.data(),"<svg width=\"%dpt\" height=\"%dpt\"",&width,&height);
           //printf("width=%d height=%d\n",width,height);
-          foundSize = count==2 && (width>500 || height>450);
-          if (foundSize) insideHeader=TRUE;
+          useNagivation = count==2 && (width>500 || height>450);
+          insideHeader = count==2;
         }
-        else if (insideHeader && !replacedHeader && line.find("<title>")!=-1)
+        else if (insideHeader && !replacedHeader && line.find("<g id=\"graph")!=-1)
         {
-          if (foundSize)
+          if (useNagivation)
           {
             // insert special replacement header for interactive SVGs
             t << "<!--zoomable " << height << " -->\n";
-            t << svgZoomHeader;
+          }
+          t << svgZoomHeader1;
+          if (useNagivation)
+          {
+            t << svgZoomHeader2;
+          }
+          if (useNagivation)
+          {
+            t << "<script type=\"application/ecmascript\">\n";
             t << "var viewWidth = " << width << ";\n";
             t << "var viewHeight = " << height << ";\n";
             if (graphId>=0)
@@ -372,15 +381,24 @@ bool DotFilePatcher::run() const
               t << "var sectionId = 'dynsection-" << graphId << "';\n";
             }
             t << "</script>\n";
-            t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n";
-            t << "<svg id=\"graph\" class=\"graph\">\n";
+          }
+          t << "<script type=\"application/ecmascript\" xlink:href=\"" << relPath << "svg.min.js\"/>\n";
+          t << "<svg id=\"graph\" class=\"graph\">\n";
+
+          if (useNagivation)
+          {
             t << "<g id=\"viewport\">\n";
           }
+          else
+          {
+            t << line;
+          }
+          line="";
           insideHeader=FALSE;
           replacedHeader=TRUE;
         }
       }
-      if (!insideHeader || !foundSize) // copy SVG and replace refs,
+      if (!insideHeader || !useNagivation) // copy SVG and replace refs,
                                        // unless we are inside the header of the SVG.
                                        // Then we replace it with another header.
       {
@@ -388,7 +406,7 @@ bool DotFilePatcher::run() const
         t << replaceRef(line,map.relPath,map.urlOnly,map.context,"_top");
       }
     }
-    else if ((i=findIndex(line.str(),reSVG))!=-1)
+    else if (line.find("SVG")!=-1 && (i=findIndex(line.str(),reSVG))!=-1)
     {
       //printf("Found marker at %d\n",i);
       int mapId=-1;
@@ -412,7 +430,7 @@ bool DotFilePatcher::run() const
         t << line.mid(i);
       }
     }
-    else if ((i=findIndex(line.str(),reMAP))!=-1)
+    else if (line.find("MAP")!=-1 && (i=findIndex(line.str(),reMAP))!=-1)
     {
       int mapId=-1;
       t << line.left(i);
@@ -437,7 +455,7 @@ bool DotFilePatcher::run() const
         t << line.mid(i);
       }
     }
-    else if ((i=findIndex(line.str(),reFIG))!=-1)
+    else if (line.find("FIG")!=-1 && (i=findIndex(line.str(),reFIG))!=-1)
     {
       int mapId=-1;
       int n = sscanf(line.data()+i+2,"FIG %d",&mapId);
@@ -463,22 +481,27 @@ bool DotFilePatcher::run() const
     {
       t << line;
     }
-    lineNr++;
   }
+  if (isSVGFile && interactiveSVG && !useNagivation) t << "</svg>\n";
+
   fi.close();
-  if (isSVGFile && interactiveSVG_local && replacedHeader)
+  if (isSVGFile && interactiveSVG && replacedHeader)
   {
     QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg";
-    t << substitute(svgZoomFooter,"$orgname",stripPath(orgName));
+    if (useNagivation)
+    {
+      t << substitute(svgZoomFooter1,"$orgname",stripPath(orgName));
+    }
+    t << svgZoomFooter2;
     t.flush();
     fo.close();
     // keep original SVG file so we can refer to it, we do need to replace
     // dummy link by real ones
-    fi.open(tmpName,std::ifstream::in);
-    fo.open(orgName.str(),std::ofstream::out | std::ofstream::binary);
+    fi = Portable::openInputStream(tmpName);
+    fo = Portable::openOutputStream(orgName);
     if (!fi.is_open())
     {
-      err("problem opening file %s for reading!\n",tmpName.c_str());
+      err("problem opening file %s for reading!\n",qPrint(tmpName));
       return FALSE;
     }
     if (!fo.is_open())
@@ -498,7 +521,7 @@ bool DotFilePatcher::run() const
     fo.close();
   }
   // remove temporary file
-  thisDir.remove(tmpName);
+  thisDir.remove(tmpName.str());
   return TRUE;
 }
 
@@ -509,7 +532,7 @@ bool DotFilePatcher::run() const
 static bool readSVGSize(const QCString &fileName,int *width,int *height)
 {
   bool found=FALSE;
-  std::ifstream f(fileName.str(),std::ifstream::in);
+  std::ifstream f = Portable::openInputStream(fileName);
   if (!f.is_open())
   {
     return false;