• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

SAX, jak obsłużyć stream pliku XML

+1 głos
110 wizyt
pytanie zadane 22 kwietnia 2021 w JavaScript przez icytower Obywatel (1,950 p.)

Cześć.

stack: node.js, typescript, sax

Streamuję plik XML przy użyciu wbudowanego modułu node fs oraz paczki sax. XML ma następującą strukturę:


<?xml version="1.0" encoding="UTF-8" ?>
<offers>
  <offer>(węzeł do którego chcę sie dostać)</offer>
  <offer>(węzeł do którego chcę sie dostać)</offer>
  <offer>(węzeł do którego chcę sie dostać)</offer>
  <offer>(węzeł do którego chcę sie dostać)</offer>
  <offer>(węzeł do którego chcę sie dostać)</offer>
<-- bardzo dużo takich węzłów, plik 2gb -->
</offers>

Potrzebuję dostać się po kolei do każdego węzła <offer> i odczytać z niego dane następnie podjąć na ich podstawie decyzję i dodać tag wewnątrz każdego węzła odpowiadający. po dodaniu tagu całość ma być zapisywana w innym pliku.
Utknąłem w miejscu, gdzie mam odczytać dane z węzła.
przykładowy węzeł <offer>
 

<offer>
        <id><![CDATA[7684]]></id>
        <name><![CDATA[Incredible Frozen Pants]]></name>
        <category><![CDATA[Tasty]]></category>
        <description><![CDATA[Atque delectus consequatur quod tempore et est sapiente et quia odio possimus eius ut et similique vero ipsam velit debitis ipsa optio sequi aliquam eius dolorum aut autem error tenetur et mollitia excepturi nihil facilis tempora ipsum sunt dignissimos natus molestiae veritatis velit eaque impedit quibusdam nesciunt sunt non laborum eos est aliquam porro fuga omnis qui explicabo vero rerum debitis perferendis in ut quis quia eveniet cum et nisi eveniet et unde nesciunt ratione qui asperiores soluta voluptatem possimus et ratione eum et cupiditate asperiores voluptatem quam id corporis natus ut eligendi consequuntur vel sunt fugit temporibus dolor iusto cumque repudiandae illum fugiat dignissimos sapiente esse cumque commodi repellendus vel et officiis animi iusto aliquam officiis qui quos doloremque dicta ab quia debitis ut dolores eaque hic dolorum rerum ut esse odit quo quia dolorem facere non non dolorum qui excepturi voluptatem totam minus reprehenderit ipsa rem et et nihil sunt repellat quo voluptatibus impedit in quod quis et natus cum ea officia sed ratione explicabo id atque aliquid cumque est voluptatibus cupiditate unde natus quia ut voluptatibus et consectetur libero saepe numquam amet eligendi qui vero mollitia est exercitationem quas nihil quas nihil debitis laboriosam totam reiciendis aut.]]></description>
        <price><![CDATA[331.69 EUR]]></price>
        <url><![CDATA[https://example.com/product/7684]]></url>
        <image_url><![CDATA[http://lorempixel.com/640/480]]></image_url>
        <opening_times><![CDATA[{"1":[{"opening":"10:00","closing":"21:00"}],"2":[{"opening":"10:00","closing":"21:00"},{"opening":"10:00","closing":"21:00"}],"3":[{"opening":"10:00","closing":"21:00"}],"4":[{"opening":"10:00","closing":"21:00"}],"5":[{"opening":"10:00","closing":"21:00"}],"6":[{"opening":"10:00","closing":"21:00"}],"7":[{"opening":"11:00","closing":"20:00"}],"timezone":"Europe/Warsaw"}]]></opening_times>
</offer>

mój aktualny kod:
 

import fs from 'fs';
import sax from 'sax';
const strict: boolean = false;
const parser: sax.SAXParser = sax.parser(strict);

try {

  const feedXMLReadStream: fs.ReadStream = fs.createReadStream('./Test xml/feed_sample.xml', {
    encoding: 'utf-8',
  });

  const feedOutXMLWriteStream: fs.WriteStream = fs.createWriteStream('./feed_out.xml', {
    encoding: 'utf-8',
  });

  var saxStream: sax.SAXStream = sax.createStream(strict, {
    // trim: true,
    lowercase: true,
  });

  saxStream.on("error", function (e) {
    // unhandled errors will throw, since this is a proper node
    // event emitter.
    console.error("saxStream error!", e)
    // clear the error
    parser.resume();
  });

  saxStream.on("opentag", function (node) {
    // same object as above

    if (node.name === 'offer') {
      console.log( node );
    }
  });

  saxStream.on("data", function (text) {
    // same object as above
    console.log("text");
  });

  saxStream.on("closetag", function (node) {
    // same object as above
    if (node === 'offer') console.log("=================", node);
  });

  saxStream.on("end", () => {
    console.log("done with saxStream");
  });

  feedXMLReadStream
    .pipe(saxStream)
    .pipe(feedOutXMLWriteStream);

  console.log('------------------------------');

} catch (err) {
  console.error('Error: \n', err);
}

czy ktoś potrafi mi podpowiedzieć jak 'chwycić' po kolei każdy węzeł <offer>?
Wczoraj cały dzień googlałem i nie udało mi się znaleźć interesujących mnie informacji.

komentarz 22 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)
 saxStream.on("data", function (text) {
    // same object as above
    console.log("text");
  });

Co tutaj pokaże console.log("text", text)? Bo teraz wypisujesz zwykły tekst zamiast argumentu text i nie wiadomo co jest w argumencie.

komentarz 22 kwietnia 2021 przez icytower Obywatel (1,950 p.)
wypisuje znaki końca linii oraz znaki tabulacji ("\n" oraz "\t")

1 odpowiedź

0 głosów
odpowiedź 22 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)

W węzłach XML-owych są dane typu CDATA, a z tego widzę, to event cdata pozwala się do nich dostać.

Logika, którą można tu zastosować żeby odczytać dane dla konkretnych tagów, to:

  • deklaracja flagi per dany tag
  • na event opentag, gdzie trzeba sprawdzić nazwę taga i gdy jest to ten oczekiwany, to ustawić flagę na true
  • na event cdata sprawdzić, czy flaga jest na true (przy większej liczbie flag / większej liczbie node-ów do przesłania sprawdzać więcej flag) i wtedy przesłać dane do strumienia wyjściowego
  • na event closetag zrobić analogicznie do obsługi eventu opentag - tutaj przestawić flagę na false

Dodatkowo, usunąłem drugi pipe do writable streamu, żeby móc ręcznie wrzucać dane (na podstawie flag) do tegoż streamu, w przeciwnym razie cały stream wejściowy był zapisywany do wyjściowego pliku.


Demo na podstawie Twojego kodu:

import * as fs from 'fs';
import * as sax from 'sax';
import { Tag } from 'sax';
const strict: boolean = false;
const parser: sax.SAXParser = sax.parser(strict, { position: true, });

const TARGET_NODE_NAME = 'description';
let foundTargetTag = false;

const feedXMLReadStream: fs.ReadStream = fs.createReadStream('./input.xml', {
  encoding: 'utf-8',
});

const feedOutXMLWriteStream: fs.WriteStream = fs.createWriteStream('./output.xml', {
  encoding: 'utf-8',
});

var saxStream: sax.SAXStream = sax.createStream(strict, {
  // trim: true,
  lowercase: true,
  position: true,
});

saxStream.on("error", function (e) {
  console.error("--- saxStream error!", e);
  parser.resume();
});

saxStream.on("opentag", function (openedTag: Tag) {
  console.log('--- openedTag:', openedTag.name);

  if (openedTag.name === TARGET_NODE_NAME) {
    foundTargetTag = true;
  }
});

saxStream.on("cdata", function (cdata) {
  console.log("--- cdata:", cdata, ' /foundTargetTag:', foundTargetTag);

  if (foundTargetTag) {
    feedOutXMLWriteStream.write(cdata);
  }
});

saxStream.on("closetag", function (closeTagName: string) {
  console.log('--- closeTagName:', closeTagName);

  if (closeTagName === TARGET_NODE_NAME) {
    foundTargetTag = false;
  }
});

saxStream.on("end", () => {
  console.log("--- done with saxStream");
});

feedXMLReadStream.pipe(saxStream);

Nie mam doświadczenia ze strumieniami, więc pewnie da się to zrobić lepiej.

komentarz 23 kwietnia 2021 przez icytower Obywatel (1,950 p.)
faktycznie to działa pięknie, to teraz jeszcze muszę wymyślić jak dodać na koniec <offer> jakiś tag. tak zeby był przed zamknięciem tego tagu. Dzięki wielkie za pomoc, zarówno tu jak i na discordzie :)
komentarz 23 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)

jeszcze muszę wymyślić jak dodać na koniec <offer> jakiś tag. tak zeby był przed zamknięciem tego tagu

W if-ie wewnątrz callbacka obsługującego event closetag lub na event cdata (po wstawieniu treści wejściowej) możesz dodać:

feedOutXMLWriteStream.write('<some_tag>lorem ipsum</some_tag>')

 

komentarz 23 kwietnia 2021 przez icytower Obywatel (1,950 p.)
Też o tym myślałem. Gdyby to działało to byłby sztos. jednak wtedy dodaje tylko ten tag, nie dodaje reszty z oryginalnego pliku. Może jakoś połączyć read i write stream?
komentarz 23 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)

W jakim sensie reszty z oryginalnego pliku? Ogólnie w demie, które wyżej napisałem, to nie ma żadnego dodawania reszty, bo celowo wyciąłem pipe do pliku wyjściowego, aby móc dodawać tylko co, się chce, za pomocą metody write na streamie

komentarz 23 kwietnia 2021 przez icytower Obywatel (1,950 p.)

w sensie plik wyściowy wygląda tak:
 

<dodany_tag>(...)</dodany_tag><dodany_tag>(...)</dodany_tag><dodany_tag>(...)</dodany_tag><dodany_tag>(...)</dodany_tag>

nie jest on wewnątrz taga <offer>. W otpucie jest tylko to co jest w .write() więc albo muszę jakoś wsadzić do zmiennej treść całego taga <offer> albo w trakcie write streamu w odpowiednim momencie 'wstrzyknąć' tag, który chcę dodać.

komentarz 23 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)

To dodaj tag otwierający i zamykający albo w eventach opentag i closetag, albo po prostu przy .write(), którym wrzucana jest docelowa treść.

komentarz 23 kwietnia 2021 przez icytower Obywatel (1,950 p.)
chodzi o to, ze nie mam zawartości początkowej, do której chcę dokleić tag.
komentarz 23 kwietnia 2021 przez ScriptyChris Mędrzec (180,980 p.)
O jakiej zawartości początkowej mówisz?
komentarz 23 kwietnia 2021 przez icytower Obywatel (1,950 p.)
edycja 23 kwietnia 2021 przez icytower

o zawartości tagu <offer> na końcu, którego chcę dodać swój tag, którego treść jest zależna od zawartości tego tagu.
aktualny kod jest tutaj.

Edit.
Chodzi mi o to, żeby dane szły z ReadStream do WriteStream i żebym mógł coś dodać do tego streamu w odpowiednim momencie(czyli przed tagiem </offer>.

Podobne pytania

0 głosów
1 odpowiedź 85 wizyt
pytanie zadane 28 stycznia 2020 w JavaScript przez TheMartian Początkujący (250 p.)
+1 głos
0 odpowiedzi 98 wizyt
pytanie zadane 31 lipca 2016 w PHP przez edwin Początkujący (410 p.)
+6 głosów
3 odpowiedzi 285 wizyt

88,328 zapytań

136,921 odpowiedzi

305,575 komentarzy

58,600 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Sklep oferujący ćwiczenia JavaScript, PHP, rozmowy rekrutacyjne dla programistów i inne materiały

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...