使用OpenXML替换word doc中的图像

时间:2010-05-11 11:13:56

标签: .net ms-word openxml

继我上一期提问here

之后

OpenXML看起来可能完全符合我的要求,但文档很糟糕。一小时的谷歌搜索没有让我更接近找出我需要做什么。

我有一个word文档。我想以这样的方式将图像添加到该word文档(使用单词),然后我可以在OpenXML中打开文档并替换该图像。应该够简单,是吗?

我假设我应该能够将我的图像'占位符'赋予某种ID,然后使用GetPartById找到图像并替换它。这是正确的方法吗?这是什么ID?你如何使用Word添加它?

我能找到的任何远程相似的例子都是从ML开始构建整个word文档开始的,这真的没什么用处。

编辑:我发现用新图片替换媒体文件夹中的图片会更容易,但是再次找不到如何执行此操作的任何指示。< / p>

9 个答案:

答案 0 :(得分:32)

虽然OpenXML的文档不是很好,但是有一个很好的工具可以用来查看现有Word文档的构建方式。如果您安装了OpenXml SDK,则它随附 Open XML Format SDK \ V2.0 \ tools 目录下的 DocumentReflector.exe 工具。

Word文档中的图像由图像数据和分配给它的ID组成,该ID在文档正文中引用。您的问题似乎可分为两部分:在文档中查找图片的ID ,然后为其重新编写图像数据

要查找图像的ID,您需要解析MainDocumentPart。图像作为绘图元素存储在运行中

<w:p>
  <w:r>
    <w:drawing>
      <wp:inline>
        <wp:extent cx="3200400" cy="704850" /> <!-- describes the size of the image -->
        <wp:docPr id="2" name="Picture 1" descr="filename.JPG" />
        <a:graphic>
          <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
            <pic:pic>
              <pic:nvPicPr>
                <pic:cNvPr id="0" name="filename.JPG" />
                <pic:cNvPicPr />
              </pic:nvPicPr>
              <pic:blipFill>
                <a:blip r:embed="rId5" /> <!-- this is the ID you need to find -->
                <a:stretch>
                  <a:fillRect />
                </a:stretch>
              </pic:blipFill>
              <pic:spPr>
                <a:xfrm>
                  <a:ext cx="3200400" cy="704850" />
                </a:xfrm>
                <a:prstGeom prst="rect" />
              </pic:spPr>
            </pic:pic>
          </a:graphicData>
        </a:graphic>
      </wp:inline>
    </w:drawing>
  </w:r>
</w:p>

在上面的示例中,您需要找到blip元素中存储的图像的ID。你如何找到它取决于你的问题,但如果你知道原始图像的文件名,你可以看看docPr元素:

using (WordprocessingDocument document = WordprocessingDocument.Open("docfilename.docx", true)) {

  // go through the document and pull out the inline image elements
  IEnumerable<Inline> imageElements = from run in Document.MainDocumentPart.Document.Descendants<Run>()
      where run.Descendants<Inline>().First() != null
      select run.Descendants<Inline>().First();

  // select the image that has the correct filename (chooses the first if there are many)
  Inline selectedImage = (from image in imageElements
      where (image.DocProperties != null &&
          image.DocProperties.Equals("image filename"))
      select image).First();

  // get the ID from the inline element
  string imageId = "default value";
  Blip blipElement = selectedImage.Descendants<Blip>().First();
  if (blipElement != null) {
      imageId = blipElement.Embed.Value;
  }
}

然后,当您拥有图像ID时,可以使用它来重写图像数据。我想你会这样做:

ImagePart imagePart = (ImagePart)document.MainDocumentPart.GetPartById(imageId);
byte[] imageBytes = File.ReadAllBytes("new_image.jpg");
BinaryWriter writer = new BinaryWriter(imagePart.GetStream());
writer.Write(imageBytes);
writer.Close();

答案 1 :(得分:17)

我想更新这个帖子,并为了其他人的利益,在上面添加Adam的答案。

实际上,我在前几天(在亚当发布他的答案之前)成功地破解了一些工作代码,但这非常困难。文档真的很差,并没有很多信息。

我不知道Adam在他的回答中使用的InlineRun元素,但诀窍似乎是到达Descendants<>属性然后你可以漂亮解析任何像普通XML映射的元素。

byte[] docBytes = File.ReadAllBytes(_myFilePath);
using (MemoryStream ms = new MemoryStream())
{
    ms.Write(docBytes, 0, docBytes.Length);

    using (WordprocessingDocument wpdoc = WordprocessingDocument.Open(ms, true))
    {
        MainDocumentPart mainPart = wpdoc.MainDocumentPart;
        Document doc = mainPart.Document;

        // now you can use doc.Descendants<T>()
    }
}

一旦你有了它,搜索东西就相当容易了,尽管你必须弄清楚所谓的东西。例如,<pic:nvPicPr>Picture.NonVisualPictureProperties等,

正如亚当正确说的那样,你需要找到替换图像的元素是Blip元素。但是你需要找到与你想要替换的图像相对应的正确光点。

Adam使用Inline元素展示了一种方式。我只是直接潜入并寻找所有的图片元素。我不确定哪种方式更好或更强大(我不知道文档之间xml结构的一致性如何导致代码破坏)。

Blip GetBlipForPicture(string picName, Document document)
{
    return document.Descendants<Picture>()
         .Where(p => picName == p.NonVisualPictureProperties.NonVisualDrawingProperties.Name)
         .Select(p => p.BlipFill.Blip)
         .Single(); // return First or ToList or whatever here, there can be more than one
}

参见Adam的XML示例,了解这里的不同元素,看看我在搜索什么。

blip在Embed属性中有一个ID,例如:<a:blip r:embed="rId4" cstate="print" />,这样做是将Blip映射到Media文件夹中的图像(如果你看到所有这些文件夹和文件)将.docx重命名为.zip并解压缩)。您可以在_rels\document.xml.rels

中找到映射

<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png" />

所以你需要做的是添加一个新图像,然后将这个blip指向你新创建的图像的id:

// add new ImagePart
ImagePart newImg = mainPart.AddImagePart(ImagePartType.Png);
// Put image data into the ImagePart (from a filestream)
newImg .FeedData(File.Open(_myImgPath, FileMode.Open, FileAccess.Read));
// Get the blip
Blip blip = GetBlipForPicture("MyPlaceholder.png", doc);
// Point blip at new image
blip.Embed = mainPart.GetIdOfPart(newImg);

我认为这只是孤立的媒体文件夹中的旧图像,这是不理想的,虽然可能它可以说是垃圾收集它的聪明。可能有更好的方法,但我找不到它。

无论如何,你有它。这个帖子现在是关于如何在网络上任何地方交换图像的最完整的文档(我知道这一点,我花了几个小时搜索)。所以希望有些人会发现它很有用。

答案 2 :(得分:7)

在看到这个帖子之前,我尝试了解如何做到这一点同样有趣。非常有用的答案人。

如果您知道包中图像的名称,选择ImagePart的一种简单方法是检查Uri


ImagePart GetImagePart(WordprocessingDocument document, string imageName)
{
    return document.MainDocumentPart.ImageParts
        .Where(p => p.Uri.ToString().Contains(imageName)) // or EndsWith
        .First();
}

然后你可以做


var imagePart = GetImagePart(document, imageName);
var newImageBytes = GetNewImageBytes(): // however the image is generated or obtained

using(var writer = new BinaryWriter(imagePart.GetStream()))
{
    writer.Write(newImageBytes);
}

答案 3 :(得分:3)

以下代码将从指定文档(文件名)中检索图像,并使用内部文件名将它们保存到D:\ TestArea文件夹中。这个页面上的答案帮助我找到了解决方案。

注意:此解决方案无法帮助某人替换word doc中的图像,但是在我搜索如何从word doc中检索图像时,这是我能找到的唯一/最接近的链接;为了防止其他人在同一条船上,我在这里发布我的解决方案。

private void ProcessImages(string filename)
{
    var xpic = "";
    var xr = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";

    using (WordprocessingDocument document = WordprocessingDocument.Open(filename, true)) 
    {
        var imageParts = 
            from paragraph in document.MainDocumentPart.Document.Body
                from graphic in paragraph.Descendants<Graphic>()
                    let graphicData = graphic.Descendants<GraphicData>().FirstOrDefault()
                        let pic = graphicData.ElementAt(0)
                            let nvPicPrt = pic.ElementAt(0).FirstOrDefault()
                            let blip = pic.Descendants<Blip>().FirstOrDefault()
                            select new 
                            {
                                Id = blip.GetAttribute("embed",xr).Value,
                                Filename = nvPicPrt.GetAttribute("name",xpic).Value
                            };

        foreach(var image in imageParts)
        {
            var outputFilename = string.Format(@"d:\TestArea\{0}",image.Filename);
            Debug.WriteLine(string.Format("Creating file: {0}",outputFilename));

            // Get image from document
            var imageData = document.MainDocumentPart.GetPartById(image.Id);

            // Read image data into bytestream
            var stream = imageData.GetStream();
            var byteStream = new byte[stream.Length];
            int length = (int)stream.Length;
            stream.Read(byteStream, 0, length);

            // Write bytestream to disk
            using (var fileStream = new FileStream(outputFilename,FileMode.OpenOrCreate))
            {
                fileStream.Write(byteStream, 0, length);
            }
        }
    }
}

答案 4 :(得分:2)

我喜欢这个部分,因为关于这个主题有很多不好的文档,并且在尝试使上述答案工作了好几个小时之后。我提出了自己的解决方案。

我如何给Image一个tagName:

enter image description here

首先,我选择要在word中替换的图像,并为其命名(例如&#34; toReplace&#34;)然后我遍历图纸选择具有正确tagName的图像并编写我自己的图像它的位置。

private void ReplaceImage(string tagName, string imagePath)
{
    this.wordDoc = WordprocessingDocument.Open(this.stream, true);
    IEnumerable<Drawing> drawings = this.wordDoc.MainDocumentPart.Document.Descendants<Drawing>().ToList();
    foreach (Drawing drawing in drawings)
    {
        DocProperties dpr = drawing.Descendants<DocProperties>().FirstOrDefault();
        if (dpr != null && dpr.Name == tagName)
        {
            foreach (DocumentFormat.OpenXml.Drawing.Blip b in drawing.Descendants<DocumentFormat.OpenXml.Drawing.Blip>().ToList())
            {
                OpenXmlPart imagePart = wordDoc.MainDocumentPart.GetPartById(b.Embed);
                using (var writer = new BinaryWriter(imagePart.GetStream()))
                {
                    writer.Write(File.ReadAllBytes(imagePath));
                }
            }
        }
    }
}

答案 5 :(得分:1)

为了获取图像并将它们复制到文件夹,您可以使用更简单的方法

        System.Collections.Generic.IEnumerable<ImagePart> imageParts =  doc.MainDocumentPart.ImageParts;

        foreach (ImagePart img in imageParts)
        {
          var uri = img.Uri;
          var fileName = uri.ToString().Split('/').Last();
          var fileWordMedia = img.GetStream(FileMode.Open);
          string imgPath = mediaPath + fileName;//mediaPath it is folder
          FileStream fileHtmlMedia = new FileStream(imgPath, FileMode.Create);
          int i = 0;
          while (i != (-1))
          {
              i = fileWordMedia.ReadByte();
              if (i != (-1))
              {
                  fileHtmlMedia.WriteByte((byte)i);
              }
          }
          fileHtmlMedia.Close();
          fileWordMedia.Close();

        }

答案 6 :(得分:1)

@Ludisposed excellent answer非常适合我,但是我花了一点力气才弄清楚如何首先在Word中实际设置图像名称。对于其他不会说德语的人,这是这样做的方法:

在MS Word中,单击图像,然后在主页功能区中,选择功能区中的“选择”->“选择窗格”,以在右侧导航中显示图像列表:

MS Word Selection Pane

然后您可以在选择窗格中单击图像的名称/标签以更改其名称:

Changing an Image name in the selection pane in MS Word

完成后,您可以使用Open XML SDK 2.5生产率工具查看如何将文本合并到Open XML文件中。

enter image description here

我已经将@Ludisposed的解决方案略微扩展为可重用的方法,并对代码进行了调整,以便传入空字节数组将触发从文档中删除图像的操作:

/// <summary>
/// Replaces the image in a document with the new file bytes, or removes the image if the newImageBytes parameter is null.
/// Relies on a the image having had it's name set via the 'Selection Pane' in Word
/// </summary>
/// <param name="document">The OpenXML document</param>
/// <param name="oldImagesPlaceholderText">The placeholder name for the image set via Selection in Word</param>
/// <param name="newImageBytes">The new file. Pass null to remove the selected image from the document instead</param>
public void ReplaceInternalImage(WordprocessingDocument document, string oldImagesPlaceholderText, byte[] newImageBytes)
{
    var imagesToRemove = new List<Drawing>();

    IEnumerable<Drawing> drawings = document.MainDocumentPart.Document.Descendants<Drawing>().ToList();
    foreach (Drawing drawing in drawings)
    {
        DocProperties dpr = drawing.Descendants<DocProperties>().FirstOrDefault();
        if (dpr != null && dpr.Name == oldImagesPlaceholderText)
        {
            foreach (Blip b in drawing.Descendants<Blip>().ToList())
            {
                OpenXmlPart imagePart = document.MainDocumentPart.GetPartById(b.Embed);

                if (newImageBytes == null)
                {
                    imagesToRemove.Add(drawing);
                }
                else
                {
                    using (var writer = new BinaryWriter(imagePart.GetStream()))
                    {
                        writer.Write(newImageBytes);
                    }
                }
            }
        }

        foreach (var image in imagesToRemove)
        {
            image.Remove();
        }
    }
}

答案 7 :(得分:0)

openXml文档非常简洁,其中大部分都需要花费太多时间。 我正在做一项特定的任务,并希望分享解决方案。我希望它能帮助人们,他们可以节省您的时间。 我必须在文本中获得特定位置的图片,特别是如果它是Run的对象。

 static string RunToHTML(Run r)
       {
            string exit = "";
            OpenXmlElementList list = r.ChildElements;
            foreach (OpenXmlElement element in list)
            {
                if (element is DocumentFormat.OpenXml.Wordprocessing.Picture)
                {
                    exit += AddPictureToHtml((DocumentFormat.OpenXml.Wordprocessing.Picture)element);
                    return exit;
                }
            }

更具体地说,我需要以html格式翻译文档的段落。

 static string AddPictureToHtml(DocumentFormat.OpenXml.Wordprocessing.Picture pic)
        {
            string exit = "";
            DocumentFormat.OpenXml.Vml.Shape shape = pic.Descendants<DocumentFormat.OpenXml.Vml.Shape>().First();
            DocumentFormat.OpenXml.Vml.ImageData imageData = shape.Descendants<DocumentFormat.OpenXml.Vml.ImageData>().First();                 
            //style image
            string style = shape.Style;
            style = style.Replace("width:", "");
            style = style.Replace("height:", "");
            style = style.Replace('.', ',');
            style = style.Replace("pt", "");
            string[] arr = style.Split(';');
            float styleW = float.Parse(arr[0]);//width picture
            float styleH = float.Parse(arr[1]);//height picture
            string relationId = imageData.RelationshipId;
            var img = doc.MainDocumentPart.GetPartById(relationId);
            var uri = img.Uri;//path in file
            var fileName = uri.ToString().Split('/').Last();//name picture
            var fileWordMedia = img.GetStream(FileMode.Open);
            exit = String.Format("<img src=\"" + docPath+uri+ "\" width=\""+styleW+"\" heigth=\""+styleH+"\" > ");
            return exit;
        }

uri它是.docx文件中的图片路径,例如:&#34; test.docx / media / image.bmp&#34; 使用这个信息图片,你可以得到图片

static void SavePictures(ImagePart img, string savePath)
        {
                var uri = img.Uri;
               var fileName = uri.ToString().Split('/').Last();
                var fileWordMedia = img.GetStream(FileMode.Open);
                string imgPath = savePath + fileName;
                FileStream fileHtmlMedia = new FileStream(imgPath, FileMode.Create);
                int i = 0;
                while (i != (-1))
                {
                    i = fileWordMedia.ReadByte();
                    if (i != (-1))
                    {
                        fileHtmlMedia.WriteByte((byte)i);
                    }
                }
                fileHtmlMedia.Close();
                fileWordMedia.Close();       
        }

答案 8 :(得分:0)

好的,谢谢所有帮助我的人。我的目标比替换图像更简单,主要是提取Word文档中的所有图像。我发现这段代码为我完成了工作,包括所需的扩展。

随时使用:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8"/>
        <title>FAM</title>
        <script type="text/javascript" src="jquery-min.js"></script>
        <script type="text/javascript" src="jquery-ui-min.js"></script>
        <script type="text/javascript" src="d3.v4.min.js"></script>      
        <link rel="stylesheet" type="text/css" href="jquery-ui.min.css"/>
        <script type="text/javascript">
            $(function () {
                var svg = d3.select('#svf');

                var container = svg.append('g');
                var object = null, line = null, path = null, i = 0, j = 0;
                var pointspath = new Array();

                function cuadricula(container) {
                    for (i = 0; i < 860; i = i + 10) {
                        container.append('line').attr('x1', i).attr('y1', 0).attr('x2', i).attr('y2', 860).attr('stroke', '#CFCECE').attr('stroke-width', 1).attr('fill', 'none');
                    }

                    for (j = 0; j < 460; j = j + 10) {
                        container.append('line').attr('x1', 1).attr('y1', j).attr('x2', 860).attr('y2', j).attr('stroke', '#CFCECE').attr('stroke-width', 1).attr('fill', 'none');
                    }
                }

                cuadricula(container);

                function main() {
                    var coords = d3.mouse(this), x = coords[0], y = coords[1];
                    var lnk = $('#dv_conv').find('a[data-pressed="1"]');
                    var conv = lnk.attr('data-conv');

                    if (conv == 1) {
                        var grp = container.append('g').attr('class', 'gwoman');
                        grp.append('circle').attr('class', 'woman').attr('name', '').attr('age', '').attr('job', '').attr('cx', x).attr('cy', y).attr('r', 20).attr('stroke-width', 2).attr('stroke', '#000').attr('fill', 'transparent');
                        grp.on('click', clicked).call(d3.drag().on('start', dragstarted).on('drag', dragged_woman).on('end', dragended))
                    }

                    if (conv == 2) {
                        container.append('rect').attr('nombre', '').attr('edad', '').attr('ocupacion', '').attr('x', x).attr('y', y).attr('width', 40).attr('height', 40).attr('stroke-width', 2).attr('stroke', '#000').attr('fill', 'transparent').on('click', clicked).call(d3.drag().on('start', dragstarted).on('drag', dragged_rect).on('end', dragended));
                    }

                    if (conv == 5) {
                        container.append('text').attr('x', x).attr('y', y).text('Hello World').on('click', clicked).call(d3.drag().on('start', dragstarted).on('drag', dragged_text).on('end', dragended));
                    }

                    if (conv == 7) {
                        pointspath[pointspath.length] = [x, y];
                        var lineGenerator = d3.line();
                        var pathData = lineGenerator(pointspath);
                        if (!path) {
                            path = container.append('path').attr('d', pathData).attr('stroke', '#000').attr('stroke-width', 2).attr('fill', 'none').on('click', clicked).call(d3.drag().on('start', dragstarted).on('drag', dragged_path).on('end', dragended));
                        } else {
                            path.attr('d', pathData);
                        }
                    }
                }

                function mousedown() {
                    var lnk = $('#dv_conv').find('a[data-pressed="1"]');
                    var conv = lnk.attr('data-conv');

                    if (conv == 3) {
                        var m = d3.mouse(this);
                        line = container.append('line').attr('x1', m[0]).attr('y1', m[1]).attr('x2', m[0]).attr('y2', m[1]).attr('stroke', '#000').attr('stroke-width', 2).attr('fill', 'transparent').on('click', clicked).call(d3.drag().on("start", dragstarted).on("drag", dragged_line).on("end", dragended));
                        svg.on('mousemove', mousemove);
                    }

                    if (conv == 6) {
                        var m = d3.mouse(this);
                        var lineGenerator = d3.line().curve(d3.curveCardinal);

                        pointspath[pointspath.length] = [m[0], m[1]];
                        pointspath[pointspath.length] = [m[0], m[1]];

                        var pathData = lineGenerator(pointspath);
                        path = container.append('path').attr('d', pathData).attr('stroke', '#000').attr('stroke-width', 1).attr('fill', 'transparent').on('click', clicked).call(d3.drag().on("start", dragstarted).on("drag", dragged_path).on("end", dragended));
                        svg.on('mousemove', mousemove);
                    }
                }

                function mousemove() {
                    var lnk = $('#dv_conv').find('a[data-pressed="1"]');
                    var conv = lnk.attr('data-conv');
                    if (conv == 3) {
                        var m = d3.mouse(this);
                        line.attr('x2', m[0]).attr('y2', m[1]);
                        console.log('x=' + m[0] + 'y=' + m[1]);
                    }

                    if (conv == 6) {
                        var lineGenerator = d3.line().curve(d3.curveCardinal);
                        var m = d3.mouse(this);
                        pointspath[pointspath.length] = [m[0], m[1]];
                        var pathData = lineGenerator(pointspath);
                        path.attr('d', pathData);
                    }
                }

                function mouseup() {
                    svg.on('mousemove', null);
                    svg.on('click', null);
                }

                function dragstarted(d) {
                    d3.select(this).raise().classed('active', true);
                }

                function dragged_circle(d) {
                    d3.select(this).attr('cx', +d3.select(this).attr('cx') + d3.event.dx);
                    d3.select(this).attr('cy', +d3.select(this).attr('cy') + d3.event.dy);
                }

                function dragged_rect(d) {
                    d3.select(this).attr('x', +d3.select(this).attr('x') + d3.event.dx);
                    d3.select(this).attr('y', +d3.select(this).attr('y') + d3.event.dy);
                }

                function dragged_text(d) {
                    d3.select(this).attr('x', +d3.select(this).attr('x') + d3.event.dx);
                    d3.select(this).attr('y', +d3.select(this).attr('y') + d3.event.dy);
                }

                function dragged_line(d) {
                    this.x = this.x || 0;
                    this.y = this.y || 0;
                    this.x += d3.event.dx;
                    this.y += d3.event.dy;
                    d3.select(this).attr('transform', 'translate(' + this.x + ',' + this.y + ')');
                }

                function dragged_path(d) {
                    this.x = this.x || 0;
                    this.y = this.y || 0;
                    this.x += d3.event.dx;
                    this.y += d3.event.dy;
                    d3.select(this).attr('transform', 'translate(' + this.x + ',' + this.y + ')');
                }

                function dragged_woman() {
                    this.x = this.x || 0;
                    this.y = this.y || 0;
                    this.x += d3.event.dx;
                    this.y += d3.event.dy;
                    d3.select(this).attr('transform', 'translate(' + this.x + ',' + this.y + ')');
                }

                function dragended(d) {
                    d3.select(this).classed('active', false);
                }

                d3.select('body').on('keydown', function () {
                    if (d3.event.keyCode === 27) {
                        svg.on('mousemove', null);
                        var lnk = $('#dv_conv').find('a[data-pressed="1"]');
                        var conv = lnk.attr('data-conv');
                        if (conv == 3) {
                            svg.on('click', mousedown);
                        }

                        if (conv == 6 || conv == 7) {
                            path = null;
                            pointspath = new Array();
                        }
                    }
                });

                function clicked(d, i) {
                    var lnk = $('#dv_conv').find('a[data-pressed="1"]');
                    var conv = lnk.attr('data-conv');
                    if (conv == 20) {
                        d3.select(this).remove();
                    }
                }

                $('#lnk_circle').click(function () {
                    svg.on('click', main);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $('#lnk_path').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_rectangle').click(function () {
                    svg.on('click', main);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $('#lnk_path').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_line').click(function () {
                    svg.on('click', mousedown).on("dblclick", mouseup);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $('#lnk_path').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_path').click(function () {
                    svg.on('click', mousedown).on("dblclick", mouseup);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_text').click(function () {
                    svg.on('click', main);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $('#lnk_path').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_linepath').click(function () {
                    svg.on('click', main);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_path').attr('data-pressed', 0);
                    $('#lnk_erase').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#lnk_erase').click(function () {
                    svg.on('click', null);
                    $('#lnk_circle').attr('data-pressed', 0);
                    $('#lnk_rectangle').attr('data-pressed', 0);
                    $('#lnk_line').attr('data-pressed', 0);
                    $('#lnk_linepath').attr('data-pressed', 0);
                    $(this).attr('data-pressed', 1);
                    return false;
                });

                $('#dv_member').dialog({
                    autoOpen: false,
                    resizable: false,
                    height: 260,
                    width: 450,
                    modal: true,
                    buttons: {
                        Guardar: function () {
                            save_member();
                            $(this).dialog('close');
                        },
                        Cancelar: function () {
                            $(this).dialog('close');
                        }
                    },
                    close: function () {
                        $('#txt_name,#txt_age,#txt_job').val('');
                    }
                });

                $('#svf').on('contextmenu', '.gwoman', function () {
                    object = $(this);

                    var circle = object.find('circle.woman');
                    var cx = +circle.attr('cx'),
                            cy = +circle.attr('cy'),
                            coords = getScreenCoords(cx, cy, circle);
                    console.log(coords.x, coords.y);
                    return false;
                });

                function getScreenCoords(x, y, element) {
                    var ctm = element.get(0).getScreenCTM();
                    var xn = ctm.e + x * ctm.a + y * ctm.c;
                    var yn = ctm.f + x * ctm.b + y * ctm.d;
                    return {x: xn, y: yn};
                }

            });
        </script>
        <style>
            line,circle,rect,polyline{ shape-rendering:crispEdges;} 
        </style>                
    </head>
    <body>
        <div id="dv_fam">
            <div id="dv_conv" style="float: left;width:200px;border:1px solid #000;margin-right: 1px">
                <a href="#" id="lnk_circle" data-pressed="0" data-conv="1">Circle</a><br/>
                <a href="#" id="lnk_rectangle" data-pressed="0" data-conv="2">Rectangle</a><br/>
                <a href="#" id="lnk_line" data-pressed="0" data-conv="3">Line</a><br/>
                <a href="#" id="lnk_text" data-pressed="0" data-conv="5">Text</a><br/>
                <a href="#" id="lnk_path" data-pressed="0" data-conv="6">Path</a><br/>
                <a href="#" id="lnk_linepath" data-pressed="0" data-conv="7">Line Path</a><br/>
                <a href="#" id="lnk_erase" data-pressed="0" data-conv="20">Erase</a><br/>
            </div>

            <div id="dv_familiogram" style="float: left;width:500px;height: 500px;border:1px solid #000;overflow: hidden">
                <svg id="svf"  style="width:500px;height:500px"></svg>
            </div>

            <div id="dv_member" title="">
                <div>
                    <label>Name</label>
                    <input type="text" id="txt_name" maxlength="100"/>
                </div>
                <div>
                    <label>Job</label>
                    <input type="text" id="txt_job" maxlength="100"/>
                </div>
                <div>
                    <label>Age</label>
                    <input type="text" id="txt_age" maxlength="3"/>
                </div>
            </div>
        </div>
    </body>
</html>