2018年1月31日水曜日

C#で、SVGファイルのXMLを操作する

SVGはXMLベースのベクターデータ用フォーマットで業界標準(?)らしい。

自分が必要なのはレーザー加工用の情報が入っている「path」って要素で、円弧などもこちらで定義している。
これの編集と追加をしたかったのだけれど、面倒そうなのでもういっそ文字列操作で済まそうかと思ったが、調べてみたら意外とすっきりXmlパーサで実装できました。変な制限がつかないで済んでえがった。

※A
まず、なんだかよく分かっていないけれど、「http://www.w3.org/2000/svg」文字列をマネージャ経由で渡す必要がある。
そして、XPathというらしいけれど「/a:svg/a:g/a:path」みたいに直接パスを指定してXmlNodeListを取得できる。(「a」は名前空間?)

※B
CloneNode(…)でコピーでき、現在位置に対してInsertBefore(…)やInsertAfter(…)で挿入できる。
現在位置もNodeで普通のリスト要素のように保持できたり。


// ログ出力クラス
private LogUtil logUtil = null;

/// 要素を解析し変換する
public System.Xml.XmlDocument ConvSvg(System.Xml.XmlDocument xmlDoc)
{
    // 引数の確認
    if (xmlDoc is null) { return null; }

    try
    {
        // -- ※A ->
        // 名前空間マネージャの生成
        System.Xml.XmlNamespaceManager nsmgr = new System.Xml.XmlNamespaceManager(xmlDoc.NameTable);
        nsmgr.AddNamespace("a", "http://www.w3.org/2000/svg");

        // 「path」の取得
        System.Xml.XmlNodeList nodeList = xmlDoc.SelectNodes("/a:svg/a:g/a:path", nsmgr);
        // <- ※A --

        // 取得の確認
        if (nodeList is null || nodeList.Count <= 0)
        {
            this.logUtil.WriteLog("パス(path) が取得できませんでした。", LogUtil.LogLevel.Error);
            this.logUtil.WriteLog(" → オブジェクト は予め パス(path) に変換する必要があります", LogUtil.LogLevel.Info);
            this.logUtil.WriteLog(" → /svg/g/path が存在するか確認してください", LogUtil.LogLevel.Info);
            return null;
        }

        // 取得したパスで回る
        foreach (System.Xml.XmlNode node in nodeList)
        {
            // 図形情報(d属性?)の取得
            string dValue = null;
            try
            {
                dValue = node.Attributes["d"].Value;
            }
            catch (Exception)
            {
                dValue = null;
            }
            if (string.IsNullOrEmpty(dValue) == true) { continue; }

            // 図形情報を確認して図形の種類を取得する
            DValueTypes dValueType = this.GetDValueType(dValue);

            // style属性(「;」「:」区切り。strokeが線色?)の取得
            string sValue = null;
            try
            {
                sValue = node.Attributes["style"].Value;
            }
            catch (Exception)
            {
                sValue = null;
            }

            // style属性を確認して処理の種類を取得する
            SValueTypes sValueType = this.GetSValueType(sValue);

            // 線色による分岐
            if (sValueType == SValueTypes.Delete)
            {
                // 削除する
                node.ParentNode.RemoveChild(node);
                continue;
            }

            // 図形種類による分岐
            string[] dValues = null;
            if (dValueType == DValueTypes.LineRelative || dValueType == DValueTypes.LineAbsolute)
            {
                dValues = this.ConvLine(dValue, dValueType, sValueType);
            }
            else if (dValueType == DValueTypes.ArcRelative || dValueType == DValueTypes.ArcAbsolute)
            {
                dValues = this.ConvArc(dValue, dValueType, sValueType);
            }
            else
            {
                this.logUtil.WriteLog("図形の種類の特定に失敗しました。", LogUtil.LogLevel.Error);
                this.logUtil.WriteLog(" → 現バージョンでは「円弧(a)」「移動(m)で定義の直線」のみ対応しています。(「直線(l)で定義の直線」は非対応)", LogUtil.LogLevel.Info);
                this.logUtil.WriteLog(" → " + dValue, LogUtil.LogLevel.Info);
                continue;
            }

            // 処理結果の確認
            if (dValues is null || dValues.Length <= 0) { continue; }

            // -- ※B ->
            // 変換結果で回る
            System.Xml.XmlNode lastNode = node;
            for (int i = 0; i < dValues.Length; i++)
            {
                if (i == 0)
                {
                    //-- 1件目の場合
                    // 置き換える
                    node.Attributes["d"].Value = dValues[i];
                    continue;
                }

                //-- 以下、2件目以降の場合

                // コピーして置き換える
                System.Xml.XmlNode newNode = node.CloneNode(true);
                newNode.Attributes["id"].Value += "_" + (i + 1).ToString().PadLeft(3, '0');
                newNode.Attributes["d"].Value = dValues[i];
                node.ParentNode.InsertBefore(newNode, lastNode);

                // 現在の位置を保存する
                lastNode = newNode;
            }
            // <- ※B --
        }
    }
    catch (Exception ex)
    {
        // ログ出力
        this.logUtil.WriteLog(LogUtil.LogLevel.Delimiter);
        this.logUtil.WriteLog(ex.ToString(), LogUtil.LogLevel.Fatal);
        this.logUtil.WriteLog(LogUtil.LogLevel.Delimiter);
        this.logUtil.WriteLog("変換処理で例外が発生しました。", LogUtil.LogLevel.Fatal);

        // 戻り値のクリア
        xmlDoc = null;
    }

    // 返す
    return xmlDoc;
}


※動作しているコードの該当箇所をそのままコピーしました

0 件のコメント:

コメントを投稿