2

一部のデータを XML として保存し、ユーザーの変更を元の XML との差分として保存するので、実行時にユーザーのデータにパッチを適用できます。

元の xml の例 (一部のみ):

<module  id="">
  <title id="">
    ...
  </title>
  <actions>
    ...
  </actions>
  <zones id="" selected="right">
    <zone id="" key="right" name="Right" />
  </zones>
</module>

ユーザー diff の例 (ユーザーが selected の値を右から左に変更した場合):

<xd:xmldiff version="1.0" srcDocHash="" 
    options="IgnoreChildOrder IgnoreNamespaces IgnorePrefixes IgnoreSrcValidation " 
    fragments="no" 
    xmlns:xd="http://schemas.microsoft.com/xmltools/2002/xmldiff">
  <xd:node match="1">
    <xd:node match="3">
      <xd:change match="@selected">left</xd:change>
    </xd:node>
  </xd:node>
</xd:xmldiff>

問題は、パッチが XML ノードの順序を検索することです。順序が変更された場合、diff を適用できなくなったり、さらに悪いことに間違って適用されたりします。したがって、XID によるパッチ適用を希望します。XyDiff の C# の高性能ライブラリまたはアルゴリズムを知っている人はいますか?

4

1 に答える 1

2

現在、正常に機能する独自のソリューションを開発しました。

何をしたか:

  • 元のファイルのすべての xml ノードに一意の ID があることを確認します (レベルに関係なく)
  • 変更された各ノードの変更のみを保存するユーザー変更用のフラット xml パッチを生成します (レベル構造なし)。
  • ユーザーが値を変更した場合は、元のノードの ID を指す属性 targetid を使用して、パッチ xml に xmlpatch ノードを書き込みます。
  • ユーザーが属性を変更した場合は、新しい値を持つ属性を xmlpatch ノードに書き込みます
  • ユーザーが値を変更した場合は、その値を xmlpatch ノードに書き込みます

xml パッチは次のようになります。

<patch>
  <xmlpatch sortorder="10" visible="true" targetId="{Guid-x}" />
  <xmlpatch selected="left" targetId="{Guid-y}" />
  <xmlpatch targetId="{Guid-z}">true</xmlpatch>
</patch>

パッチ xml を生成するコードは非常に簡単です。すべての xml ノードをループし、ノードごとにすべての属性をループします。ノードのアトリビュートまたは値がオリジナルと異なる場合、アトリビュートまたは値を使用してパッチ ノードを生成します。コードは一晩で書かれたことに注意してください ;)

public static XDocument GenerateDiffGram(XDocument allUserDocument, XDocument runtimeDocument)
{
    XDocument diffDocument = new XDocument();
    XElement root = new XElement("patch");

    AddElements(root, runtimeDocument, allUserDocument.Root);

    diffDocument.Add(root);
    return diffDocument;
}

private static void AddElements(XElement rootPatch, XDocument runtimeDocument, XElement allUserElement)
{
    XElement patchElem = null;
    if (allUserElement.Attribute("id") != null 
        && !string.IsNullOrWhiteSpace(allUserElement.Attribute("id").Value))
    {
        // find runtime element by id
        XElement runtimeElement = (from e in runtimeDocument.Descendants(allUserElement.Name)
                        where e.Attribute("id") != null 
                        && e.Attribute("id").Value.Equals(allUserElement.Attribute("id").Value)
                        select e).FirstOrDefault();
        // create new patch node
        patchElem = new XElement("xmlpatch");

        // check for changed attributes
        foreach (var allUserAttribute in allUserElement.Attributes())
        {
            XAttribute runtimeAttribute = runtimeElement.Attribute(allUserAttribute.Name);
            if (!allUserAttribute.Value.Equals(runtimeAttribute.Value))
            {
                patchElem.SetAttributeValue(allUserAttribute.Name, runtimeAttribute.Value);
            }
        }

        // check for changed value
        if (!allUserElement.HasElements 
        && !allUserElement.Value.Equals(runtimeElement.Value))
        {
            patchElem.Value = runtimeElement.Value;
        }
    }

    // loop through all children to find changed values
    foreach (var childElement in allUserElement.Elements())
    {
        AddElements(rootPatch, runtimeDocument, childElement);
    }

    // add node for changed value
    if (patchElem != null 
        && (patchElem.HasAttributes 
        || !string.IsNullOrEmpty(patchElem.Value)))
    {
        patchElem.SetAttributeValue("targetId", allUserElement.Attribute("id").Value);
        rootPatch.AddFirst(patchElem);
    }
}

実行時に、パッチ xml に保存された変更にパッチを当てます。targetid で元のノードを取得し、属性と値を上書きします。

public static XDocument Patch(XDocument runtimeDocument, XDocument userDocument, string modulePath, string userName)
{
    XDocument patchDocument = new XDocument(userDocument);

    foreach (XElement element in patchDocument.Element("patch").Elements())
    {
        // get id of the element
        string idAttribute = element.Attribute("targetId").Value;
        // get element with id from allUserDocument
        XElement sharedElement = (from e in runtimeDocument.Descendants()
                        where e.Attribute("id") != null 
                        && e.Attribute("id").Value.Equals(idAttribute)
                        select e).FirstOrDefault();

        // element doesn't exist anymore. Maybe the admin has deleted the element
        if (sharedElement == null)
        {
            // delete the element from the user patch
            element.Remove();
        }
        else
        {
            // set attributes to user values
            foreach (XAttribute attribute in element.Attributes())
            {
                if (!attribute.Name.LocalName.Equals("targetId"))
                {
                    sharedElement.SetAttributeValue(attribute.Name, attribute.Value);
                }
            }

            // set element value
            if (!string.IsNullOrEmpty(element.Value))
            {
                sharedElement.Value = element.Value;
            }
        }
    }

    // user patch has changed (nodes deleted by the admin)
    if (!patchDocument.ToString().Equals(userDocument.ToString()))
    {
        // save back the changed user patch
        using (PersonalizationProvider provider = new PersonalizationProvider())
        {
            provider.SaveUserPersonalization(modulePath, userName, patchDocument);
        }
    }

    return runtimeDocument;
}
于 2012-06-19T08:23:50.650 に答える