Dynamic mesh in OpenFOAM

features in OpenFOAM is dynamic meshing. But there is almost no documentation. Here we build an example.

Case setup

Our case is a 100×35 mm rectangle with the 8x25mm obstacle on the top side. This obstacle will move left to right leaving 5mm space to vertical walls.

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      blockMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

convertToMeters 0.001;

vertices
(
    (0.0 0 0) (0.0 0 1)
    (5.0 0 0) (5.0 0 1)
    (13.0 0 0) (13.0 0 1)
    (100.0 0 0) (100.0 0 1)
    (0.0 10.0 0) (0.0 10.0 1)
    (5.0 10.0 0) (5.0 10.0 1)
    (13.0 10.0 0) (13.0 10.0 1)
    (100.0 10.0 0) (100.0 10.0 1)
    (0.0 35.0 0) (0.0 35.0 1)
    (5.0 35.0 0) (5.0 35.0 1)
    (13.0 35.0 0) (13.0 35.0 1)
    (100.0 35.0 0) (100.0 35.0 1)
);
blocks
(
    hex (0 2 10 8 1 3 11 9) (5 10 1) simpleGrading (1 1 1)
    hex (2 4 12 10 3 5 13 11) (8 10 1) simpleGrading (1 1 1)
    hex (4 6 14 12 5 7 15 13) (87 10 1) simpleGrading (1 1 1)
    hex (8 10 18 16 9 11 19 17) (5 25 1) simpleGrading (1 1 1)
    hex (12 14 22 20 13 15 23 21) (87 25 1) simpleGrading (1 1 1)
);

edges
(
);

patches
(
    wall walls
    (
        (0 2 3 1)
        (2 4 5 3)
        (4 6 7 5)
        (6 14 15 7)
        (14 22 23 15)
        (16 8 9 17)
        (8 0 1 9)
    )
    patch obstacle
    (
        (20 12 13 21)
        (12 10 11 13)
        (10 18 19 11)
    )
    patch atmosphere
    (
        (22 20 21 23)
        (18 16 17 19)
    )
);

// ************************************************************************* //

Initial water level is 20mm. This is achieved by system/setFieldsDict file

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "system";
    object      setFieldsDict;
}

defaultFieldValues
(
    volScalarFieldValue alpha.water 0
    volVectorFieldValue U ( 0 0 0 )
);

regions
(
    boxToCell
    {
        box ( 0 0 0 ) ( 0.100 0.020 0.001 );
        fieldValues
        (
            volScalarFieldValue alpha.water 1
        );
    }
 );

Note: here is no convertToMeters parameter, so all dimensions are in meters.

Other options are taken from “Breaking of a dam” tutorial.

Dynamic meshing setup

To use dynamic meshing features, two files should be created. The first is constant/dynamicMeshDict. It specifies dynamic mesh type and it’s parameters. In our case, it looks like below:

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      dynamicMeshDict;

}
motionSolverLibs ( "libfvMotionSolvers.so"  );
dynamicFvMesh    movingConeTopoFvMesh;

movingConeTopoFvMeshCoeffs
{
  motionVelAmplitude (0.082 0 0); // 82mm/sec through X axis
  motionVelPeriod 1.5; // realy, its SEMIperiod

  leftEdge -0.020;
  leftObstacleEdge  0.030;
  rightObstacleEdge 0.050;

  left
  {
    minThickness 0.1e-3;
    maxThickness 0.2e-3;

  }

  right
  {
    minThickness 0.1e-3;
    maxThickness 0.2e-3;

  }
}

Here, we choose movingConeTopoFvMesh type with 82 mm/sec velocity amplitude and 1.5 sec semi-period (see details below).

The second file we should provide is constant/polyMesh/meshModifiers. It contains a list of mesh modifiers that will be applied to the mesh. Our file is:

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "constant";
    object      meshModifiers;

}
2
(
  right
  {
    type layerAdditionRemoval;
    faceZoneName rightExtrusionFaces;
    minLayerThickness 0.0008;
    maxLayerThickness 0.0012;
    oldLayerThickness -1;
    active on;
  }
   left
   {
    type layerAdditionRemoval;
    faceZoneName leftExtrusionFaces;
    minLayerThickness 0.0008;
    maxLayerThickness 0.0012;
    oldLayerThickness -1;
    active on;
  }
);

Here we define two modifiers with layerAdditionRemoval type. It’s parameters will be discussed below.

moivingConeTopoFvMesh

This modifier moves all points between two face zones named leftExtrusionFaces and rightExtrusionFaces. Points are moved according the law \mathbf{dx} = \mathbf{v_0} \sin\left(\dfrac{\pi t}{T}\right) dt, where \mathbf{v_0} is velocity amplitude specified by motionVelAmplitude entry of movingConeTopoFvMeshCoeffs dictionary. Semi-period T corresponds to motionVelPeriod value.

Other dictionary entries are used only when moivingConeTopoFvMesh is run in “automatic” mode. It happens when neither face zones, not point zones, cell zones or mesh modifiers is specified. In this case, moivingConeTopoFvMesh creates two layerAdditionRemoval modifiers, that are controlled via left and right sub-dictionaries. It also creates two face zones, one for each modifier. The first zone is named leftExtrusionFaces and includes all faces with centre’s X coordinate equal to -7.0 mm. The second zone is named rightExtrusionFaces and includes all faces with centre’s X coordinate equal to -3.5 mm. Note this values are hard-coded. It seems like originally leftObstacleEdge and rightObstacleEdge parameters were planned to set these values, but somebody forgot to use them.

layerAdditionRemoval

This modifier adds or removes cell layers near the face zone specified.

When is applied by the dynamic mesh engine (like moivingConeTopoFvMesh), it first checks the thickness of all cells touching the face in the face zone. If an average thickness increased relative to previous call and maximum thickness is greater than maxLayerThickness, than layerAdditionRemoval adds new layer of cells. In the same manner, if an average thickness decreased relative to previous call and maximum thickness is less than maxLayerThickness, than layerAdditionRemoval removes one layer of cells.

Entry oldLayerThickness specifies value for the first comparison with average layer thickness, when there is no value from the previous step.

Special note should be done about cell selection. Internal faces has two cells – owner and neighbour. It’s normal points from the cell with a smaller ID to the cells with the larger one. Boundary faces, obviously, have only owner cell, and their normals point out the cell. It seems like for both internal and boundary faces an algorithm chooses cell that is pointed to by the face normal. It means that boundary face normals should always be inverted.

Face zones

Face zone is a list of faces with normal flipping specified. List of all face zones is specified in constant/polyMesh/faceZones file.

FoamFile                            
{                                   
    version     2.0;                
    format      ascii;              
    class       regIOobject;        
    location    "constant/polyMesh";
    object      faceZones;          
}                                   

2
(
rightExtrusionFaces
{
    type faceZone;
    faceLabels List 5 (1 3 5 7 9);
    flipMap    List  5 (0 1 1 0 0);
}
leftExtrusionFaces
{
    type faceZone;
    faceLabels List 3 (2 4 6);
    flipMap    List  3 (0 1 1);
}
)

Each list item contains it’s type faceZone, list of face labels and list of boolean values that indicate if face normal should be flipped (relative to it’s original direction) or no.

The last thing is important, because layerAdditionRemoval uses normal direction for cell selection.

Face zone creation

To produce face zones, use topoSet utility. Here is the file system/topoSetDict:

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      topoSetDict;

}

actions
(
{
        name    fLeft;
        type    faceSet;
        action  new;
        source  boxToFace;
        sourceInfo
        {
            box (0.0049 0 0) (0.0051 0.035 0.001);
        }
}
{
        name    fRight;
        type    faceSet;
        action  new;
        source  boxToFace;
        sourceInfo
        {
            box (0.0129 0 0) (0.0131 0.035 0.001);
        }
}
{
        name    cLeft;
        type    cellSet;
        action  new;
        source  boxToCell;
        sourceInfo
        {
            box (0.004 0 0) (0.005 0.035 0.001);
        }
}
{
        name    cRight;
        type    cellSet;
        action  new;
        source  boxToCell;
        sourceInfo
        {
            box (0.013 0 0) (0.014 0.035 0.001);
        }
}
{
        name    rightExtrusionFaces;
        type    faceZoneSet;
        action  new;
        source  setsToFaceZone;
        sourceInfo
        {
            faceSet fRight;
            cellSet cRight;
            flip    true;
        }
}
{
        name    leftExtrusionFaces;
        type    faceZoneSet;
        action  new;
        source  setsToFaceZone;
        sourceInfo
        {
            faceSet fLeft;
            cellSet cLeft;
            flip    true;
        }
}
);

Face sets

In this file, we first create two face sets fLeft and fRight with all faces with X coordinate equal to 5mm and 13mm. Note that not only obstacle’s faces are used, but internal faces below them too.

cells

Next, we define cell (yes, cell) sets cLeft and cRight. The first set includes cells left to the fLeft faces. In other words, it is the rightmost cell column before the obstacle. In the same manner, the second cell set is the leftmost column after the obstacle.

The last step is a face zones definition. It is done by setsToFaceZone source. As appears from it’s name, it creates face zone from the face set. But it has additional parameter cellSet. It seems like it refers to the cell set that face normals should point out from. More precisely, according to the setsToFaceZone.C:148, topoSet inverts normal if

  • it is boundary face and it’s owner is not in cellSet
  • it is internal face and it’s neighbour (but not owner) is in cellSet

The flip flag inverts the result of these rules.

normals

These steps produce exactly what we need. Really, by default, all faces from fLeft have normals directed to the right (see fig.). All they also belongs to cells from cLeft. It means they aren’t touched by the rules above, but are then flipped by the flip flag.

As for faces from fRight set, they splits into two parts.

The first one is the obstacle’s right side (i.e. top part of fRight). It’s faces by default look to the left (as a boundary faces, that look outside domain). They also have owners from cRight cell set. As a result, they will be flipped only by the flip flag.

The second part of fRight includes faces below the obstacle right side. Initially, their normals look to the right. Note these faces have no owners in cRight cell set, but have neighbours. It implies that they will be flipped twice: by the second rule from the list above, and by the flip flag. So, they will remain the same as default, i.e. pointing to the right.

zones

Resulting face zones are shown on the the figure right. Note that all normals look into cells that will be affected by the layer modifiers.

Advertisements

CFD calculation of the organ key touch

Geometry

real

First, let’s describe what we want to build. Organ pallet is placed inside large box with under long rectangular hole. Pallet’s rotation axis is near hole’s end. Wind chest hole length varies between stops and organs, so let’s take length 300mm and width 12mm. The pallet itself will be 318mm in length and 30mm width, placed below the hole.

Obviously, our case is symmetric along the pallet’s vertical middle plane. It allows using symmetry plane boundary conditions in the case. Also pallet’s bottom surface and space below it can be excluded, if we assume pressure inside the wind chest to be zero.

Grid

The case is just a 3x4x6 array of boxes with some ones excluded. The top blocks line is a hole (strictly, the half of hole) in the chest. Three other “floors” are chest internal area. It has 2x1x4 (in blocks) cut that is pallet surface.

blocks

The whole mesh is 35mm width and 358mm long. The top blocks are 6mm wide, corresponding to 12mm chest hole. The bottom cut is 15mm wide. As about heights, they vary with the pallet descend value, labelled as d1 on the draw. Rear block height d2 is about 0.1 of d1.

grading

All blocks have non-uniform grading. Cell sizes are smaller near walls and larger in free air.

Boundary conditions

There are four boundaries:
* walls – pallet and chest surfaces
* inlet – air inside chest
* outlet – atmosphere
* symmPl – symmetry plane through pallet’s middle

bounds

Air flow is pressure-driven, so inlet and outlet BC have the fixed pressure and zeroGradient velocity. As for other fields, they are token from OpenFOAM tutorials. I am not sure they are absolutely right, but I hope…

Here is the whole boundary condition table:

walls inlet outlet symmPl
p zeroGradient fixedValue -700 zeroGradient symmetryPlane
U fixedValue (0 0 0) zeroGradient symmetryPlane
epsilon epsilonWallFunction 7.65e-4 zeroGradient zeroGradient symmetryPlane
k kqWallFunction zeroGradient zeroGradient symmetryPlane
nut nutkWallFunction calculated 0 calculated 0 symmetryPlane

Computations

Our case should be run multiple times with different pallet descend values. OpenFOAM has a family of dynamic mesh solvers, but it is too hard to learn it. Instead, lets create new folder result with standard OpenFOAM case structure. Then just run the solver multiple times. After each run, copy all fields to the folder result/[d], where [d] means pallet’s descend value. Also copy constant/polyMesh/points file to the result/[d]/polyMesh folder. ParaView will read these points and update scene for each time step.

These steps is done with the next script

#!/bin/bash

# Pallet descend values
for d in '0.0125' '0.025' '0.05' '0.075' '0.1'
do  b=$(echo "scale=3;$d/10.0" | bc)

  # Generate mesh
  m4 -D dFront=$d -D dBack=$b constant/polyMesh/blockMeshDict.m4 > constant/polyMesh/blockMeshDict
  blockMesh >blockMesh.log
  if [ "$?" -ne "0" ]; then
    echo 'Error in blockMesh' $?
    break
  fi

  # Remove iteration folders except '0'
  ls | grep -P '^[0-9]*[1-9][0-9]*$' | xargs rm -r -d
  simpleFoam >log
  if [ $? -ne 0 ]; then
    echo 'Error in simpleFoam' $?
    break
  fi

  # Create resulting folder.
  # -p key created all parent folders if need
  mkdir -p result/$d/polyMesh
  
  # Find directory of the last iteration
  lastdir=$(ls | grep -P '^[0-9]+' | xargs printf '%05d\n' | sort | tail -n 1)
  
  # Remove trailing spaces
  ld=$(echo $lastdir | sed 's/^0*//')
  echo "Diameter $d processed with $ld iterations"
  
  # Copy fields and points
  cp -r $ld/* result/$d
  cp -r constant/polyMesh/points result/$d/polyMesh/
done

cp -r constant result
echo >result/case.foam

Results

torque

Resulting torque is shown on the plot. Red line represents computation results, green line shows predicted values near pallet closed state. Right axis shows a force in kilograms that is created at the pallet’s free end.

As you can see, the maximum torque 0.46 Nm is achieved at 1mm pallet descend. During further pallet descending, the torque decreases to 0.225 Nm.

As for pallet closing, the simulation is stable only to 0.125mm. But we can find torques for less values by hands.

When pallet is totally closed, the atmosphere pressure is uniformly distributed at the chest hole. The torque is found as
\int_0^l p w z dz = p w \frac{l^2}{2} = 700 \times 0.006 \times 0.3^2 \times \frac{1}{2} = 0.189 N/m

pclose
When pallet is almost, but not fully, closed, pressure remains uniformly distributed at the hole, and changes linearly from -700 to 0 between pallet and chest board, as shown on the figure. The torque created can be found as
700\times 0.06\times\frac{0.3^2}{2} + 700\times 0.009\times \frac{1}{2} \times\frac{0.3^2}{2} = 700\times 0.0105\times\frac{0.3^2}{2} = 0.33N/m

To get the force in kg at the pallet’s end, lets divide the torque on the pallet length and gravity acceleration. Also don’t forget to double the result, because we simulated only the half of the pallet:
W = \frac{2\times T}{0.3\times 9.81}

force
The pallet’s weight is shown on the plot. Red line represents simulation results, green line shows predicted values near pallet closing.

Now we can describe the force at the pallet’s end in different positions:

  • closed: 128g
  • touched: 224g
  • 1mm: 312g
  • open: 152g

Hmm… too large… The only reason I can see is pipe hole influence. Maybe, the pressure above the pallet is not equal to atmosphere, but larger, because pipe holes resists to air escape from the chest.

Signal-Slot diagram using Doxygen and XSLT

Qt signal-slot analysis using Doxygen and XSLT

Sometimes I need to see connections between signals and slots in my Qt projects. It seems like now there is no software than can do this. Many programs can draw call graphs, but none of them works with Qt signals.

I decided to write own C++ parser, but shortly I had released that it is a very complicated task. It will take a lot of time without any appropriate result.

So, I need anythig that can parse C++ files for me. And I found that Doxygen can do it. It produces XML files with class structure description and code listing, that are easy to parse.

Lets start with a couple of classes for exploring Doxygen output:

class SenderClass : public QObject
{
      Q_OBJECT
public:
    explicit SenderClass(QObject *parent = 0);

signals:
    void mySignal_01(int, bool);
};

class RecieverClass : public QObject
{
    Q_OBJECT
public:
    RecieverClass(QObject *parent = 0);
    virtual ~RecieverClass() {}
private slots:
    void on_sc_mySignal_01(int,bool) {}
private:
    SenderClass sc;
};

SenderClass does nothing except emitting mySignal_01 signal. The only action of RecieverClass is connection to this signal:

RecieverClass::RecieverClass(QObject *parent) :
    QObject(parent)
{
    connect(&sc, SIGNAL(mySignal_01(int,QList*)),
            this, SLOT(on_sc_mySignal_01(int,QList*)));
}

Now lets process it with the next Doxygen file:

GENERATE_HTML          = NO
GENERATE_LATEX         = NO
GENERATE_XML           = YES

EXTRACT_ALL            = YES
EXTRACT_PRIVATE        = YES

REFERENCED_BY_RELATION = YES
REFERENCES_RELATION    = YES

This will create xml directory with some XML files. Three of them relates to Sender class:

  • classSender.xml
  • sender_8.cpp
  • sender_8.h

The classSender.xml file contains info about Sender class structure

<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.8.6">
  <compounddef id="classSender" kind="class" prot="public">
    <compoundname>Sender</compoundname>
    <basecompoundref prot="public" virt="non-virtual">QObject</basecompoundref>
    <includes refid="sender_8h" local="no">sender.h</includes>
    <sectiondef kind="public-func">
      <memberdef kind="function" id="classSender_1afbe94a9a42b0101deb08204393ec9669" prot="public" static="no" const="no" explicit="yes" inline="no" virt="non-virtual">
        <type></type>
        <definition>Sender::Sender</definition>
        <argsstring>(QObject *parent=0)</argsstring>
        <name>Sender</name>
        <param>
          <type>QObject *</type>
          <declname>parent</declname>
          <defval>0</defval>
        </param>
        <location file="/home/resu/work/reftest/src/sender.h" line="10" column="1" bodyfile="/home/resu/work/reftest/src/sender.cpp" bodystart="3" bodyend="6"/>
      </memberdef>
    </sectiondef>
    <sectiondef kind="signal">
      <memberdef kind="signal" id="classSender_1a2d121184a23b605453b050f420bf0975" prot="public" static="no" const="no" explicit="no" inline="no" virt="non-virtual">
        <type>void</type>
        <definition>void Sender::mySignal_01</definition>
        <argsstring>(int, bool)</argsstring>
        <name>mySignal_01</name>
        <param>
          <type>int</type>
        </param>
        <param>
          <type>bool</type>
        </param>
        <location file="/home/resu/work/reftest/src/sender.h" line="13" column="1"/>
      </memberdef>
    </sectiondef>
  </compounddef>
</doxygen>

It containt <compounddef> element with several <sectiondef> children for different member and access types:
– private/protected/public variables
– private/protected/public functions
– private/protected/public slots
– signals

Each <sectiondef> includes <memberdef> elements with it’s kind (function, variable, signal or slot), type, arguments and location, i.e. file and lines where it’s body is placed

Another usefull file is sender_8cpp.xml. This is a program listing with highlight options for keywords, strings, etc. We will use it for searching QObject::connect() calls for futher parsing.

Before proceed, it will be helpfull to combine all XML files generated by Doxygen into large one. The next command does it:

saxonb-xslt -s index.xml -xsl:combine.xsl

To get connection data, we need to make the following steps:
– find QObject::connect() calls
– split it into arguments
– obtain types of sender and reciever class, signal and slot members
– build connection list
– merge it with origial all.xml file

As a result, each <memberdef> with @kind equals to 'signal' or 'slot' should get new elements showing what signals or slots it is connected to. Then we easly can use it for diagram drawing, unused member search, etc.

Connection string search

Now we need to find code lines with connection commands. It is easy, because these lines always starts with connect(, maybe with spaces.

So, the matches function can be used with the regexp '^\s*connect\s*\('. Remember that all code is contained in codeline elements splitted into highlight for different literal types (keywords, numbers, etc.). Our string is a simple text, so it is contained in a single highlight element. So, the final XPath query will be

codeline[count(highlight[matches(.,'^s*connects*(')])>0]

But the code line found with this query may not contain the whole connection command, because it often is broken into multiple lines. Fortunately, connect call always ends with semicolon, so its enough to collect all codelines until the semicolon is found.

It is done by code below:

  <xsl:template match="codeline[count(highlight[matches(.,'^s*connects*(')])>0]">
    <xsl:apply-templates select="." mode="findsemicolon"/>
  </xsl:template>

  <xsl:template match="codeline[count(highlight[contains(.,';')])=0]"
                mode="findsemicolon">
    <xsl:param name="value"/>
    <xsl:apply-templates mode="findsemicolon"
                         select="../codeline[@lineno = current()/@lineno+1]">
      <xsl:with-param name="value" select="concat($value, .)"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="codeline[count(highlight[contains(.,';')])>0]"
                mode="findsemicolon">
    <xsl:param name="value"/>
    <!-- concat($value, .) is the whole connection command -->
  </xsl:template>

Here we use findsemicolon mode, because otherwice the second and the third templates will match all strings with semicolons.

Connection string parsing

Having the whole connect call, the next step is to extract four parts of this call:
– sender
– signal
– reciever
– signal or slot

The last part can be signal because Qt allows not only signal-to-slot connection, but signal-to-signal too.

XSLT 2.0 introduces very helpful <xsl:analyze-string> element. It allows us to parse the string, capture it’s parts and then create result using regex-group function.

Here is a function that accepts connection string and returns it’s parts. It also says is the connection target signal or slot.

  <xsl:function name="mns:tokenizeconn">
    <xsl:param name="x" as="xs:string"/>
    <xsl:analyze-string select="$x"
       regex="connect((.*),s*SIGNAL((.*)),s*(.*),s*(SLOT|SIGNAL)((.*))(,Qt::.*)?);">
      <xsl:matching-substring>
        <tokens>
          <sender><xsl:value-of select="regex-group(1)"/></sender>
          <signal><xsl:value-of select="regex-group(2)"/></signal>
          <reciever><xsl:value-of select="regex-group(3)"/></reciever>
          <target>
            <type><xsl:value-of select="regex-group(4)"/></type>
            <name><xsl:value-of select="regex-group(5)"/></name>
          </target>
        </tokens>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
        <xsl:message>
          Warning: pre-matched string doesn't match regexp:
          <xsl:value-of select="$x"/>
        </xsl:message>
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </xsl:function>

Sender and reciever parsing

When sender, reciever, signal and target parts have been extracted, we may analyze them separately. Lets begin with sender and reciever. Both can be just class fields, or method calls, or call of field’s method, etc, or just this pointer. For example:

this->field.method(arg1,arg2)

As you can see, the most common case is field/method chain with sections separated by dot '.' or minus-greater pair '-&gt;'. It may be tokenized via the next regular expression:

^([a-zA-Z0-9_]+)((.*?))?(.|->)?(.*)?$

It extracts the first chain section, it’s parenthesis (it there are), section separator and the rest of the string. Using this regexp, we can write function that splits our call chain into sections:

<xsl:function name="mns:tokmbch">
  <xsl:param name="x" as="xs:string"/>
  <xsl:analyze-string select="$x"
                      regex="^([a-zA-Z0-9_]+)((.*?))?(.|->)?(.*)?$">
    <xsl:matching-substring>
      <token>
        <name><xsl:value-of select="regex-group(1)"/></name>
        <type>
          <xsl:if test="string-length(regex-group(2)) gt 0">method</xsl:if>
          <xsl:if test="string-length(regex-group(2)) = 0">field</xsl:if>
        </type>
      </token>
      <xsl:if test="string-length(regex-group(3)) gt 0">
        <xsl:copy-of select="mns:tokmbch(regex-group(4))"/>
      </xsl:if>
    </xsl:matching-substring>
  </xsl:analyze-string>
</xsl:function>

After the first section extraction, it check if there is a separator after it, and, if there is, recursively splits the rest of input. As a result, it returns the sequence of <token> elements containing name of the member called and it’s type — field or function (don’t confuse with type in C++ meaning — we will find it below).

Current class search

To find a class that current line of code belongs to, we first need to find a method containing current code line. Note that code listing and class declaration belongs to different <compounddef> elements of composed XML file. They can be tied via <memberdef>s element <location> It has three attributes:

  • bodyfile – the full path to the file containing method declaration,
  • bodystart – the first line of the method body,
  • bodyend – the last one.

At first, lets take a file the current code line belongs to:

<xsl:variable name="codeline" select="."/>
<xsl:variable name="file" select="$codeline/ancestor::compounddef"/>

Then lets find a method whose definition is in this file, and that starts before the current code line and ends after it:

    <xsl:variable name="method" select="$codeline/ancestor::doxygen//memberdef[
       location/@bodyfile = $file/location/@file and
       number(location/@bodystart) le number($codeline/@lineno) and
       number(location/@bodyend)   ge number($codeline/@lineno)]"/>

Now we can find a class:

    <xsl:variable name="class" select="$method/ancestor::compounddef"/>

Sender and reciever type detection

Having a current class and sender/reciever splitted into individual member calls, we now can find the class of the last section of the chain. To do it, we find the member with a name and kind (funtion or variable) specified by the first section of the call chain, then get it’s type (for a function it will be return type). This type (that is guaranteed to be a class) can be used to process next section of the call chain.

Lets write a function for it:

<xsl:function name="mns:parsmbch">
  <xsl:param name="tokens"/>
  <xsl:param name="class"/>

At first, take the first token in a chain:

  <xsl:variable name="token" select="$tokens[1]"/>

Then, choose a kind of class member we are going to search.
It will be used for kind attribute.

  <xsl:variable name="mkind">
    <xsl:if test="$token/type = 'method'">function</xsl:if>
    <xsl:if test="$token/type = 'field'">variable</xsl:if>
  </xsl:variable>

Next, we may find a class. If the token’s <name> element is 'this', then desired class is the current method class itself. Otherwice, this class may be found by refid attribute of the <ref> element inside <type> of class member with name equal to chain’s section name:

<xsl:variable name="typeid">
  <xsl:if test="$token/name = 'this'">
    <xsl:value-of select="$class/@id"/>
  </xsl:if>
  <xsl:if test="$token/name != 'this'">
    <xsl:variable name="member"
            select="$class//memberdef[@kind=$mkind and name=$token/name]"/>
    <xsl:value-of select="$member/type/ref/@refid"/>
  </xsl:if>
</xsl:variable>

Here we obtain class ID, not the class itself. It is done because <xsl:sequence> inside a variable will create a copy of the node, so we loose a node’s parent.

Now obtain the class definition itself:

<xsl:variable name="type" select="$class/../compounddef[@id=$typeid]"/>

If the token we process is the last in the chain, return it. Otherwise, recursively call function with the found class as a owner for the next token:

  <xsl:if test="count($tokens) = 1">
    <xsl:sequence select="$type"/>
  </xsl:if>
  <xsl:if test="count($tokens) > 1">
    <xsl:sequence select="mns:parsmbch($tokens[fn:position()>=2],$type)"/>
  </xsl:if>
</xsl:function>

Signal and slot type detection

Now lets look at the signal and slot signatures. We already obtained it when parsing the whole connection line. We have <signal> element with a signal signature and <target> element with it’s signature and type (is it a signal or a slot).

<xsl:function name="mns:parssgsl">
<xsl:param name="sgsl"/>
<xsl:param name="class"/>

Introduce a variable target for processing both name/type pair and a single name the same way:

<xsl:variable name="target">
  <xsl:if test="count($sgsl/name) gt 0">
    <xsl:sequence select="$sgsl/*"/>
  </xsl:if>
  <xsl:if test="count($sgsl/name) = 0">
    <name><xsl:value-of select="$sgsl"/></name>
    <type>signal</type>
  </xsl:if>
</xsl:variable>

Split target string in two parts: name and arguments:

<xsl:variable name="nmargs">
<xsl:analyze-string select="$target/name"
   regex="([a-zA-Z0-9_]+)((.*))?">
  <xsl:matching-substring>
      <name><xsl:value-of select="regex-group(1)"/></name>
      <args><xsl:value-of select="regex-group(2)"/></args>
  </xsl:matching-substring>
  <xsl:non-matching-substring>
    <xsl:message>
      Warning: pre-matched string doesn't match regexp:
      <xsl:value-of select="$target/name"/>
    </xsl:message>
  </xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>

Now find a member with these name and type:

<xsl:sequence select="$class//memberdef[
                        @kind=lower-case($target/type) and
                        name=$nmargs/name]"/>

Emit connection information

Now return to the main part of our XSLT. We already obtained all information about connection.

<xsl:template match="codeline[count(highlight[contains(.,';')])>0]"
              mode="findsemicolon">
  <xsl:param name="value"/>

Find code line’s file, method and class, as we did above:

<xsl:variable name="codeline" select="."/>

<xsl:variable name="file" select="$codeline/ancestor::compounddef"/>

<xsl:variable name="method" select="$codeline/ancestor::doxygen//memberdef[
   location/@bodyfile = $file/location/@file and
   number(location/@bodystart) le number($codeline/@lineno) and
   number(location/@bodyend)   ge number($codeline/@lineno)]"/>

<xsl:variable name="class" select="$method/ancestor::compounddef"/>

Split connect() call:

<xsl:variable name="tokens" select="mns:tokenizeconn(concat($value, .))"/>

Obtain sender class:

<xsl:variable name="sendertokens" select="mns:tokmbch($tokens/sender)"/>
<xsl:variable name="senderclass" select="mns:parsmbch($sendertokens, $class)"/>

Get a signal member:

<xsl:variable name="signal" select="mns:parssgsl($tokens/signal, $senderclass)"/>

Do the same for reciever and target

<xsl:variable name="recievertokens" select="mns:tokmbch($tokens/reciever)"/>
<xsl:variable name="recieverclass" select="mns:parsmbch($recievertokens, $class)"/>
<xsl:variable name="target" select="mns:parssgsl($tokens/target, $recieverclass)"/>

Emit connection data

 <connection>
    <sender>
       <class><xsl:value-of select="$senderclass/@id"/></class>
       <signal><xsl:value-of select="$signal/@id"/></signal>
    </sender>
    <reciever>
       <class><xsl:value-of select="$recieverclass/@id"/></class>
       <member><xsl:value-of select="$target/@id"/></member>
       <name><xsl:value-of select="$target/name"/></name>
       <type><xsl:value-of select="$target/@kind"/></type>
    </reciever>
    <string><xsl:value-of select="concat($value,.)"/></string>
 </connection>
</xsl:template>

Now, run XSLT
saxonb-xslt -s:all.xml -xsl:conn.xsl -o:conns.xsl

Merging connection data with original file

After connection list generation, lets merge this data with original composed XML file.

First, read connection data to the variable:

<xsl:variable name="connections" select="doc('connections.xml')/connections"/>

Now, define identity transform for the whole document

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
</xsl:template>

For slot members, add recievesfrom tags with ID of signal that this slot recieves:

<xsl:template match="memberdef[@kind='slot']">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
    <xsl:call-template name="addrecieves"/>
  </xsl:copy>
</xsl:template>

Signals can both send itself and recieve another signals, so add recievesfrom and sendsto tags for it:

<xsl:template match="memberdef[@kind='signal']">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
    <xsl:call-template name="addrecieves"/>
    <xsl:call-template name="addsends"/>
  </xsl:copy>
</xsl:template>

To add signals that current target recieves, find all connection with reciever equals to the current member ID:

<xsl:template name="addrecieves">
  <xsl:apply-templates
    select="$connections/connection[reciever/member=current()/@id]/sender"/>
</xsl:template>

<xsl:template match="sender">
  <recievesfrom>
    <xsl:value-of select="signal"/>
  </recievesfrom>
</xsl:template>

In the same way add targets the current signal sends to:

<xsl:template name="addsends">
  <xsl:apply-templates
    select="$connections/connection[sender/signal=current()/@id]/reciever"/>
</xsl:template>

<xsl:template match="reciever">
  <sendsto>
    <xsl:value-of select="member"/>
  </sendsto>
</xsl:template>

Angle brackets in WordPress

Writing my first post (that now will be the second) I had troubles with angle brackets in source code.
So, lets look at different ways of source code insertion and.
As a test case, use a prase

Any XSLT contains <xsl:stylesheet> or &lt;xsl:transform&gt; with <xsl:template> inside element

enclosing highlighted text to all possible code tags.

[code]
Any XSLT

contains <xsl:stylesheet> or &lt;xsl:transform&gt; with <xsl:template>

element

Triple quotes ```
Any XSLT

contains <xsl:stylesheet> or <xsl:transform> with <xsl:template>

element

<pre>
Any XSLT

contains  or <xsl:transform> with <xsl:template>

element

<code>
Any XSLT contains or <xsl:transform> with <xsl:template> element

Single quotes `
Any XSLT contains or &lt;xsl:transform&gt; with <xsl:template> element

Summarize results in the next table:

<x> &lt;x&gt; &#60;x&#62;
[code] <x> &lt;x&gt; <x>
``` <x> <x> <x>
<pre> <x> <x>
<code> <x> <x>
` &lt;x&gt; <x>

So, the shortcode [code] is the most usefull for code blocks with angle brackets. As for inline case, we may use <code> with angle brackets or signle quotes with char codes.