4

I am working with jQuery like elements from the cheerio library to manipulate SVG images.

These objects represent XML nodes and have a hierarchical internal structure.

I am writing a function (in JavaScript of course ) that visits each child node and, if it is a Basic Shape (ellipse, circle, rect...), calculates a set of points for the shape and invokes a user supplied function with the point set and element information.

Along the way, the function keeps track of all transform operations that have been specified upto that node, so that when it generates the point set for a shape, it will apply the accumulated scale, rotate, translate etc operations for that node.

The user supplied function will do things like generate polygon objects or calculate bounding boxes.

Is there a standard design pattern for this kind of function? It seems vaguely like a Visitor pattern but in that case the nodes would have a Visitor interface and invoke an operation themselves. I don't want to modify the cheerio objects. Furthermore, the nodes don't know where they are in the tree and cannot supply their transformation context.

This is not a difficult routine to write but I am interested to know if there is any formal theory that's relevant?

  • 2
    I don't want to modify the cheerio objects -- Then you can't use the Visitor pattern, unless you wrap it in another object that can hold your ongoing state operations, and then construct and return a brand new tree when you're done. Alternatively, you can write a recursive function that constructs a new tree from the existing one. – Robert Harvey Mar 14 '17 at 18:38
  • 1
    @RobertHarvey what about having the visitor emit an event containing the modified copy? – RubberDuck Mar 14 '17 at 21:22
  • @RubberDuck: Sure, that will work. I don't know if you could still call that a Visitor, though. – Robert Harvey Mar 14 '17 at 21:26
  • Fair point. ANTLR has listeners, which sounds like where we're heading. Could be a useful example to reference. – RubberDuck Mar 14 '17 at 22:16
1

For completeness, I thought I would post what I actually came up with:

//Traverses the SVG tree and invokes userFnc when ever it encounters    
//a Basic Shape node    
function shapesTraverser( jQsvgs, userFnc, transformers = Transformer() ){

        jQsvgs.each( function(){
            let jQ = $( this );

            //parse the SVG 'transform' attribute and append it to
            //the sequence of tranformation operations
            transformers = transformers.cat( jQ.transform() );

            if(isShapeElement( jQ ) ){
                stop = userFnc( jQ, transformers );
            }
            else{//recursive call
                shapes.shapesTraverser( jQ.children(), userFnc, transformers )
            }

        } );
    }

//Rides on the shapeTraverser.  For each Basic Shape, it calculates
//a set of points, applies the transformation ops, and passes the
//result to userFnc
function pointsTraverser( jQsvgs, userFnc ){

        let shapesFunc = function( jQ, transformers ){

            let points = toPoints( jQ );
            points = transformers( ...points );

            userFnc( jQ, points );

        }

        this.shapesTraverser( jQsvgs, shapesFunc )

    }

// Uses pointsTraverser to calculate a bounding box for the whole
// SVG object
function boundingBox( jQsvgs ){
        let box = new BoundingBox();

        function pointsFunc( jQ, points ){
            box.merge( points );
        }

        this.pointsTraverser( jQsvgs, pointsFunc );

        return box;
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.