Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Ошибка при работе с XML


Автор: Dom 22.7.2007, 11:34
Есть xml-файл, следующего содержания

Код

<?xml version="1.0" encoding="windows-1252"?>
<ISPs version="1.033">
    <ISP>
        <Name>AlphaTV +</Name>
        ...
    </ISP>
    <ISP>
        <Name>AlphaTV -</Name>
        ...
    </ISP>
    <ISP>
        <Name>Aries</Name>
        ...
    </ISP>
    <ISP>
        <Name>Aries +</Name>
        ...
    </ISP>
</ISPs>


Написал функцию DefineISPNames для извлечения значений нод 'Name' в StringList. Работает отлично.
Код

var   MainXMLDoc : IXMLDocument;
...
MainXMLDoc := TXMLDocument.Create(nil);
MainXMLDoc.NodeIndentStr := #9;
MainXMLDoc.Options := MainXMLDoc.Options + [doNodeAutoIndent];
...
MainXMLDoc.LoadFromFile(XMLFileName);
MainXMLDoc.Active := true;
...

function DefineISPNames : TStringList;
var
i : integer;
Node : IXMLNode;
begin
  Result := TStringList.Create;
  Result.Sorted := true;
  Node := MainXMLDoc.DocumentElement;
  for i := 0 to Node.ChildNodes.Count-1 do begin
    Result.Add(Node.ChildNodes[i].ChildNodes['Name'].NodeValue);
  end;
end;

После создаю новую ноду <ISP> и ее детей и добавляю ее в начало списка. Затем снова вызываю DefineISPNames. Но в этот раз она считывает только одну первую ноду и на второй ноде (i =1) выдает ошибку в строке 20 Result.Add(Node.ChildNodes[i].ChildNodes['Name'].NodeValue)
Ошибка вот какая -  Project wsnake.exe raised exception class EOleException with message 'This operation can not be performed with a Node of type PCDATA'. Process stopped. Use Step or Run to continue.
Что это за тип ноды PCDATA? Что-то ничего не могу найти про это ни в хелпах ни в инете.

Кстати, если после добавления новой ноды <ISP> сохранить xml-файл, то в нем сохраняются все ноды, и добавленная и старые, и все на своих местах. Т.е. структура документа правильная.

Надеюсь на вашу помощь. smile

Автор: ALeXandrK 26.7.2007, 18:01
Цитата

После создаю новую ноду <ISP> и ее детей и добавляю ее в начало списка. Затем снова вызываю DefineISPNames. Но в этот раз она считывает только одну первую ноду и на второй ноде (i =1) выдает ошибку в строке 20 Result.Add(Node.ChildNodes[i].ChildNodes['Name'].NodeValue)


Так может ты неправильно её создаешь (ноду). Прогони построчно через debuger и посмотри как изменяется твой документ 
после добавления в него новой ноды. Именно через debuger, а не сохраняя документ.

Кстати, а если сохраненный документ с новой нодой, попытаться прогнать твоей функцией, тоже вылетит такая же ошибка на том же месте?

PCDATA - анализируемые символьные данные или данные, которые обычно отображаются в браузере.

P.s.: Раз уж приводишь код, так приводи весь, чтобы можно было самому погонять программу smile 

Автор: Dom 26.7.2007, 18:55
Цитата(ALeXandrK @ 26.7.2007,  18:01)
Кстати, а если сохраненный документ с новой нодой, попытаться прогнать твоей функцией, тоже вылетит такая же ошибка на том же месте?

Нет, если сохраненный документ снова открыть и считывать сожержимое нод этой функцией, то ошибки не возникает, и все ноды в т.ч. и новая отображаются правильно в создаваемом списке. Даже был такой топорный вариант - пересохранять файл, каждый раз после добавления ноды. Но рука до сих пор не поднялась на подобное. smile

С debuger'ом попробую разобраться, раньше с ним не работал.  Но спасибо за подсказку.

Код сейчас приведу.

У меня подозрение на несоместимость данных, т.е. я пытаюсь загнать в стринг содержимое ноды, а там как-будто не строка содержится. Может какое-то преобразование данных нужно...

Автор: ALeXandrK 26.7.2007, 19:31
Цитата

У меня подозрение на несоместимость данных, т.е. я пытаюсь загнать в стринг содержимое ноды, а там как-будто не строка содержится. Может какое-то преобразование данных нужно...

Самое простое, чтобы проверить, это перевести все в тип String и сохранять и читать его как String.
Если это не поможет, значит ошибка не в типе.

Нет... тут точно нужно debugerом гнать. 

Цитата

С debuger'ом попробую разобраться, раньше с ним не работал.

Тем более нельзя упускать такой шанс научиться им пользоваться - это все-равно рано или поздно
придется делать. А сложного там ничего нет... так что дерзай  smile 

P.s.: Если уж с нодами разобрался, то дебагер - это плёвое дело smile 

Автор: Dom 26.7.2007, 19:53
Код

var   MainXMLDoc : IXMLDocument;

//открытие xml-файла
procedure TForm1.XML1Click(Sender: TObject);
var
ISPNameList : TStringList;
begin
  OpenDialog3.Title := 'Open XML file';
  OpenDialog3.DefaultExt := 'xml';
  OpenDialog3.Filter := 'Xml files (*.xml)|*.xml|All files (*.*)|*.*';
  if OpenDialog3.Execute then begin
    MainXMLFileName := OpenDialog3.FileName;
    MainXMLDoc := TXMLDocument.Create(nil);
    MainXMLDoc.NodeIndentStr := #9;
    MainXMLDoc.Options := MainXMLDoc.Options + [doNodeAutoIndent];
    MainXMLDoc.LoadFromFile(MainXMLFileName);
    MainXMLDoc.Active := true;
    ISPNameList := DefineISPNames;    //тут ошибки не возникает
  end;
end;

//вызов процедуры добавления ноды
//на AddNewISPForm расположены Edit'ы и CheckBox'ы
//значения которых и надо добавить в новую ноду
procedure TForm1.Button8Click(Sender: TObject);
begin
  AddNewISPForm := TAddNewISPForm.Create(Self);
  if AddNewISPForm.ShowModal = mrOK then begin
    AddNewISPNodeToXML;
  end;
end;

//функция которая возвращает список значений нод Name
function DefineISPNames : TStringList;
var
i : integer;
Node : IXMLNode;
begin
  Result := TStringList.Create;
  Result.Sorted := true;
  Node := MainXMLDoc.DocumentElement;
  for i := 0 to Node.ChildNodes.Count-1 do begin
//в этом месте возникает ошибка при вызове функции из AddNewISPNodeToXML
//при i=1, т.е. при попытке прочесть вторую ноду в списке
//хотя новая нода добавляется первой в списке и ее данные считываются без проблем
    Result.Add(Node.ChildNodes[i].ChildNodes['Name'].NodeValue);  end;
end;

//процедура добавления новой ноды и ее содержимого
procedure AddNewISPNodeToXML;
var
ISPNode : IXMLNode;
ISPNameList : TStringList;
begin
    MainXMLDoc.DocumentElement.AddChild('ISP',0);    //добавляю ISP ноду в начало списка
    ISPNode := MainXMLDoc.DocumentElement.ChildNodes[0];
    ISPNode.AddChild('Name').NodeValue := NameEdit.Text;
    ISPNode.AddChild('Description').NodeValue := DescriptionEdit.Text;
    ISPNode.AddChild('Prefix').NodeValue := PrefixEdit.Text;
    ISPNode.AddChild('MinShare').NodeValue := StrToInt64(MinShareEdit.Text);
    if MinShareCheckBox.State = cbChecked then
      ISPNode.ChildNodes['MinShare'].Attributes['enable'] := 'true';

    MainXMLDoc.SaveToFile(MainXMLFileName);
    ISPNameList := DefineISPNames;  //тут возникает ошибка внутри самой функции
end;


Вот собственно все необходимые компоненты. Надеюсь комментарии помогут разобраться в деталях.

Добавлено через 1 минуту и 4 секунды
Цитата(ALeXandrK @ 26.7.2007,  19:31)
Самое простое, чтобы проверить, это перевести все в тип String и сохранять и читать его как String.
Если это не поможет, значит ошибка не в типе.

Уже пробовал. Действительно ошибка не в типе.

Добавлено через 7 минут и 10 секунд
Цитата

Тем более нельзя упускать такой шанс научиться им пользоваться - это все-равно рано или поздно
придется делать. А сложного там ничего нет... так что дерзай   

P.s.: Если уж с нодами разобрался, то дебагер - это плёвое дело  

Я не совсем уж новичок в программировании. Но вот с дебагером еще не сталкивался. Займусь.  smile 
А с нодами разобраться несложно было. Тем более тут на форуме полно дельных советов есть и толковых кусочков кода. smile 

Это я решил для нашего хаба по-быстрому написать прогу для редактирования всех настроек, чтобы не руками лазить каждый раз. Однако процесс затянулся. smile 

Автор: ALeXandrK 26.7.2007, 20:02
А что за тип такой TAddNewISPForm?
Я понимаю, что это форма, а в чём её задача?

Автор: Dom 26.7.2007, 20:09
Не совсем верный код запостил для процедуры AddNewISPNodeToXML
Там надо добавить в начало строку
Код

with AddNewISPForm do begin


Более точная процедура бцдет выглядеть так.
Код

procedure AddNewISPNodeToXML;
var
ISPNode : IXMLNode;
ISPNameList : TStringList;
begin
  with AddNewISPForm do begin
    MainXMLDoc.DocumentElement.AddChild('ISP',0);    //добавляю ISP ноду в начало списка
    ISPNode := MainXMLDoc.DocumentElement.ChildNodes[0];
    ISPNode.AddChild('Name').NodeValue := NameEdit.Text;
    ISPNode.AddChild('Description').NodeValue := DescriptionEdit.Text;
    ISPNode.AddChild('Prefix').NodeValue := PrefixEdit.Text;
    ISPNode.AddChild('MinShare').NodeValue := StrToInt64(MinShareEdit.Text);
    if MinShareCheckBox.State = cbChecked then
      ISPNode.ChildNodes['MinShare'].Attributes['enable'] := 'true';

    MainXMLDoc.SaveToFile(MainXMLFileName);
    ISPNameList := DefineISPNames;  //тут возникает ошибка внутри самой функции
  end;
end;


Сорри. Надеюсь это не фатальная ошибка. И так ясно что присваиваются значения полей Edit.

Добавлено через 1 минуту и 28 секунд
AddNewISPForm это форма, на которой расположены Edit'ы и CheckBox'ы, значения которых и надо добавить в новую ноду

Автор: ALeXandrK 26.7.2007, 20:15
Для полного счастья прикрепи свой xml файлик smile 

Автор: Dom 26.7.2007, 20:22
Цитата(ALeXandrK @ 26.7.2007,  20:15)
Для полного счастья прикрепи свой xml файлик smile

Легко. smile 
Только не очень удивляйся всем полям в файле. Просто сейчас я сократил для простоты экспериментов процедуру добавления и отключил добавление всех дочерних нод, оставил только несколько.

Автор: ALeXandrK 26.7.2007, 20:31
Добавление проверил. Все верно... переходим на функцию DefineISPNames.
Странно, но 
Код

  for i := 0 to Node.ChildNodes.Count-1 do begin
  Result.Add(Node.ChildNodes[i].ChildNodes['Name'].NodeValue);


При открытии это цикл выполняется дважды (у меня две ноды). Т.е. всё верно,
но
Код

  for i:=0 to ISPNameList.Count-1 do
  ShowMessage(ISPNameList.ValueFromIndex[i]);


Показывает, что в списке только один элемент. Ерунда какая-то smile 

Автор: Dom 26.7.2007, 21:13
Стоп. А куда ты последний цикл вставлял?

Я выводил значения ISPNameList в ListBox вот таким образом
Код

procedure FillListBoxWithISPName(const NameList: TStringList; var ListBox: TListBox);
var
i : integer;
begin
  ListBox.Clear;
  if ListBox.Name = 'RemoveSectionListBox' then
    ListBox.AddItem('All',nil);
  for i := 0 to NameList.Count-1 do begin
    ListBox.AddItem(NameList[i],nil);
  end;
end;
...
FillListBoxWithISPName(ISPNameList,ListBox1);
и никаких проблем не возникало, все ноды на месте...

Добавлено через 2 минуты и 41 секунду
На строки 6 и 7 не обращай внимания. Это я добавляю первым элементом строку с надписью All в один из листбоксов.

Автор: ALeXandrK 26.7.2007, 21:19
Лохонусля... там все тоже ок smile 

Автор: Dom 26.7.2007, 21:26
Учусь пока обращатсья с дебаггером...
Кстати у меня Делфи 7. Уже не знаю на что и думать.

Автор: ALeXandrK 26.7.2007, 21:38
 smile Ура... нашел. Вот эти строчки нужно убрать, тогда все работает великолепно smile 
Код

    MainXMLDoc.NodeIndentStr := #9;
    MainXMLDoc.Options := MainXMLDoc.Options + [doNodeAutoIndent];


Из-за этих строк и происходит билиберда, если они очень нужны, то ищи что-то в эту тему...
видимо открытый файл нужно обновлять или что-то в этом роде, т.к. после вставки новых нод
XML вообще пуст (это в памяти - можешь убедиться через дебагер smile ). Потому и ошибка вылетает.


Автор: Dom 26.7.2007, 21:50
Шаман!  smile
Благодарю за помощь! Что-нибудь уже дальше придумаем.

Но учиться дебагингу все равно надо. Это как - купишь рыбу, будешь сыт день. Научишься ловить...  smile 

К сожалению репутацию добавить не могу. Поэтому еще раз поблагодярю за то, что помог разобраться.

Автор: ALeXandrK 26.7.2007, 21:57
Дебагинг - это неотъемлемая часть программирования.
С его помощью можно экономить уйму времени smile  и нервов smile 
Рад был помочь.

Там видимо нужно после добавления обновлять, т.е. метод Refresh или Resync вызывать 
... ну я думаю и без NodeIndent обойтись можно, если читать напрямую глазками XML документ не будешь... 
и лишнии байты сэкономишь smile 

Автор: Lemon 12.11.2007, 05:56
У меня тоже возникла подобная проблема. Вот как я ее решил.
Если в опциях XML-документа включен параметр doNodeAutoIndent или в коде имеется строчка XMLDocData.Options := [doNodeAutoIndent], то 
строчка
DataNode.ChildNodes[i].AddChild('Code').Text := '45';
даст ошибку
"this operation can not be performed with a node of type PCDATA"

i - это числовая переменная

а если не использовать числовую переменную, а писать имя узла, то ошибки возникать не будет.
Лично я не могу понять почему так происходит. А догадался чисто случайно. smile 

Рабочий пример:

    ParamNode := DataNode.AddChild('Object');
    ParamNode.AddChild('Code').Text := '45';
    ParamNode.AddChild('Name').Text := 'Test Name';

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)