Comment supprimer JavaScript et autres balises à l'aide de Python… sans importer de modules

Sep 30 2020

Pour la première partie du projet scolaire, j'essaie de comprendre comment supprimer JavaScript <script {...} >et les </script {...} >balises ainsi que tout ce qui se trouve entre <et >.

Cependant, nous ne pouvons importer aucun module (même construit en Python) car apparemment le marqueur pourrait ne pas être en mesure d'y accéder, etc.

J'ai essayé ceci:

text = "<script beep beep> hello </script boop doop woop> hello <hi> hey <bye>"
while text.find("<script") >= 0:
    script_start = text.find("<script")
    script_end = text.find(">", text.find("</script")) + 1
    text = text[:script_start] + text[script_end:]

while text.find("<") >= 0:
    script2_start = text.find("<")
    script2_end = text.find(">") + 1
    text = text[:script2_start] + text[script2_end:]

Et cela fonctionne pour les fichiers plus petits, mais le projet a à voir avec de gros fichiers texte (le test simplifié qui nous a été donné est de 10,4 Mo), donc il ne se termine pas et reste bloqué.

Quelqu'un a-t-il des idées pour le rendre plus efficace?

Réponses

2 PatrickArtner Sep 30 2020 at 17:33

Vous n'avez rien à supprimer. En fait: vous ne voulez jamais modifier les chaînes.

Les chaînes sont immuables: chaque fois que vous en "modifiez" une, vous en créez une nouvelle et vous supprimez l'ancienne. C'est un gaspillage de processeur et de mémoire.

Vous travaillez sur des fichiers - alors traitez-le par caractère:

  • souviens-toi si tu es à l'intérieur <...>ou non
  • si c'est le cas, seul le caractère important est >de sortir à nouveau
  • si à l'extérieur et que le personnage est, <vous entrez à l'intérieur et ignorez ce personnage
  • si à l'extérieur et non <vous écrivez le caractère dans la sortie (-file)

# create file
with open("somefile.txt","w") as f:
    # up the multiplicator to 10000000 to create something in the megabyte range
    f.write("<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata\n"*10)

# open file to read from and file to write to
with open("somefile.txt") as f, open("otherfile.txt","w") as out:
    # starting outside
    inside = False
    # we iterate the file line by line
    for line in f:
        # and each line characterwise
        for c in line:
            if not inside and c == "<":
                inside = True
            elif inside and c != ">":
                continue
            elif inside and c == ">":
                inside = False
            elif not inside:
                # only case to write to out
                out.write(c)

print(open("somefile.txt").read() + "\n")
print(open("otherfile.txt").read())

Production:

<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata
<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata


 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata
 hello  hello  hey  tata

Si vous n'êtes pas autorisé à utiliser directement les fichiers, lisez le fichier dans une liste qui consomme 11 + Mo de mémoire:

data = list("<script beep beep> hello </script boop doop woop> hello <hi> hey <bye> tata\n" * 10)

result = []

inside = False
for c in data:
    if inside:
        if c == ">":
            inside = False
        # else ignore c - because we are inside
    elif c == "<":
        inside = True
    else:
        result.append(c)

print(''.join(result))

c'est encore mieux que de rechercher itérativement la première occurrence de "<" dans la liste, mais cela peut nécessiter jusqu'à deux fois la mémoire de votre source (si elle ne contient aucun <..>, vous doublez la liste).

Le fonctionnement des fichiers est beaucoup plus efficace en termes de mémoire que toute modification de liste en place (ce qui serait une troisième façon de le faire).


Il y a des choses flagrantes que vous auriez également besoin de contourner, fe

<script type="text/javascript">
var i = 10;
if (i < 5) {
  // some code
}
</script>

laissera le "code" à l'intérieur.


Cela pourrait faciliter les cas de coin:

# open file to read from and file to write to
with open("somefile.txt") as f, open("otherfile.txt","w") as out:
    # starting outside
    inside = False
    insideJS = False
    jsStart = 0
    # we iterate the file line by line
    for line in f:

        # string manipulation :/ - will remove <script ...> .. </script ..>
        # even over multiple lines - probably missed some cornercases.
        while True:
          if insideJS and not "</script" in line:
              line = ""
              break

          if "<script" in line:
              insideJS = True
              jsStart = line.index("<script")
              jsEnd = len(line)
          elif insideJS:
              jsStart = 0
          
          if not insideJS:
              break

          if "</script" in line:
              jsEnd = line.index(">", line.index("</script", jsStart))+1
              line = line[:jsStart] + line[jsEnd:]
              insideJS = False
          else:
              line = line[:jsStart]

        # and each line characterwise
        for c in line:
            # ... same as above ...
1 woblob Sep 30 2020 at 18:18

Même s'il y a 2 boucles while, il s'agit toujours d'une complexité linéaire

string = "<script beep beep> hello </script boop doop woop> hello <hi> hey <bye>"
new_string = ''
i = 0
while i < len(string):
    if string[i] == "<":
        while i < len(string):
            i += 1
            if string[i] == '>':
                break
    else:
        new_string += string[i]
    i += 1

print(new_string)

Les sorties:

 hello  hello  hey 
1 Tibebes.M Sep 30 2020 at 17:49

Voici une approche avec FSA :

output = ''

NORMAL, INSIDE_TAG = range(2) # availale states

state = NORMAL # start with normal state

s = '<script beep beep> hello </script boop doop woop> hello <hi id="someid" class="some class"><a> hey </a><bye>'

for char in s:
  if char == '<': # if we encounter '<' we enter the INSIDE_TAG state
    state = INSIDE_TAG
    continue
  elif char == '>': # we can safely exit the INSIDE_TAG state
    state = NORMAL
    continue

  if state == NORMAL:
    output += char  # add the char to the output only if we are in normal state

print(output)

Si l'analyse sémantique des balises est requise, assurez-vous d'utiliser stack (peut être implémentée avec list).


Cela augmenterait la complexité, mais vous obtiendrez une vérification robuste avec FSM.

voir l'exemple suivant:

output = ''

(
  NORMAL,
  TAG_ATTRIBUTE,
  INSIDE_JAVASCRIPT,
  EXITING_TAG,
  BEFORE_TAG_OPENING_OR_ENDING,
  TAG_NAME,
  ABOUT_TO_EXIT_JS
) = range(7) # availale states

state = NORMAL # start with normal state

tag_name = ''

s = """
<script type="text/javascript">
  var i = 10;
  if (i < 5) {
    // some code
  }
</script>
<sometag>
  test string
  <a href="http://google.com"> another string</a>
</sometag>
"""

for char in s:
  # print(char, '-', state, ':', tag_name)
  if state == NORMAL:
    if char == '<':
      state = BEFORE_TAG_OPENING_OR_ENDING
    else:
      output += char
  elif state == BEFORE_TAG_OPENING_OR_ENDING:
    if char == '/':
      state = EXITING_TAG
    else:
      tag_name += char
      state = TAG_NAME
  elif state == TAG_ATTRIBUTE:
    if char == '>':
      if tag_name == 'script':
        state = INSIDE_JAVASCRIPT
      else:
        state = NORMAL
  elif state == TAG_NAME:
    if char == ' ':
      state = TAG_ATTRIBUTE
    elif char == '>':
      if tag_name == 'script':
        state = INSIDE_JAVASCRIPT
      else:
        state = NORMAL
    else:
      tag_name += char
  elif state == INSIDE_JAVASCRIPT:
    if char == '<':
      state = ABOUT_TO_EXIT_JS
    else:
      pass
      # output += char
  elif state == ABOUT_TO_EXIT_JS:
    if char == '/':
      state = EXITING_TAG
      tag_name = ''
    else:
      # output += '<'
      state = INSIDE_JAVASCRIPT
  elif state == EXITING_TAG:
    if char == '>':
      state = NORMAL

print(output)

PRODUCTION:

  test string
  another string